Merge inbound to mozilla-central. a=merge
authorOana Pop Rus <opoprus@mozilla.com>
Thu, 28 Mar 2019 12:11:35 +0200
changeset 525358 d2c82f845bfcf1ebd75e3a8aa932f17c3ac9ded8
parent 525335 c045dd97faf291904e060d8889391497d92b8353 (current diff)
parent 525357 648419554d95d6d61be119445a22e59bc33c33ab (diff)
child 525359 4e2ea1a75e878ae392e4775f2eddd9f83d1b008e
child 525365 c2adeb1c803bd48c27c81a18774bac497fb26d67
child 525378 a861743a902f1092a512923a55d905805c11b06c
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
d2c82f845bfc / 68.0a1 / 20190328101229 / files
nightly linux64
d2c82f845bfc / 68.0a1 / 20190328101229 / files
nightly mac
d2c82f845bfc / 68.0a1 / 20190328101229 / files
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
Merge inbound to mozilla-central. a=merge
browser/actors/LightWeightThemeInstallChild.jsm
browser/app/profile/firefox.js
browser/base/content/browser-webrender.js
browser/base/content/browser.js
browser/locales/en-US/chrome/browser/lightweightThemes.properties
dom/asmjscache/AsmJSCache.cpp
dom/asmjscache/AsmJSCache.h
dom/asmjscache/PAsmJSCacheEntry.ipdl
dom/asmjscache/moz.build
dom/asmjscache/test/.eslintrc.js
dom/asmjscache/test/file_slow.js
dom/asmjscache/test/mochitest.ini
dom/asmjscache/test/test_cachingBasic.html
dom/asmjscache/test/test_cachingMulti.html
dom/asmjscache/test/test_slow.html
dom/asmjscache/test/test_workers.html
dom/base/nsDOMWindowUtils.cpp
dom/indexedDB/ActorsParent.cpp
toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
toolkit/mozapps/extensions/test/xpcshell/test_theme_update.js
toolkit/profile/nsIToolkitProfile.idl
toolkit/profile/nsToolkitProfileService.cpp
toolkit/profile/nsToolkitProfileService.h
toolkit/profile/xpcshell/head.js
toolkit/profile/xpcshell/test_claim_locked.js
toolkit/profile/xpcshell/test_clean.js
toolkit/profile/xpcshell/test_create_default.js
toolkit/profile/xpcshell/test_lock.js
toolkit/profile/xpcshell/test_new_default.js
toolkit/profile/xpcshell/test_previous_dedicated.js
toolkit/profile/xpcshell/test_remove_default.js
toolkit/profile/xpcshell/test_select_default.js
toolkit/profile/xpcshell/test_single_profile_selected.js
toolkit/profile/xpcshell/test_single_profile_unselected.js
toolkit/profile/xpcshell/test_skip_locked_environment.js
toolkit/profile/xpcshell/test_snatch_environment.js
toolkit/profile/xpcshell/test_snatch_environment_default.js
toolkit/profile/xpcshell/test_steal_inuse.js
toolkit/profile/xpcshell/test_update_selected_dedicated.js
toolkit/profile/xpcshell/test_update_unknown_dedicated.js
toolkit/profile/xpcshell/test_update_unselected_dedicated.js
toolkit/profile/xpcshell/test_use_dedicated.js
toolkit/profile/xpcshell/xpcshell.ini
xpcom/base/nsINIParser.cpp
xpcom/base/nsINIParser.h
deleted file mode 100644
--- a/browser/actors/LightWeightThemeInstallChild.jsm
+++ /dev/null
@@ -1,51 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["LightWeightThemeInstallChild"];
-
-const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
-
-class LightWeightThemeInstallChild extends ActorChild {
-  handleEvent(event) {
-    let {mm} = this;
-    switch (event.type) {
-      case "InstallBrowserTheme": {
-        mm.sendAsyncMessage("LightWeightThemeWebInstaller:Install", {
-          baseURI: event.target.baseURI,
-          principal: event.target.nodePrincipal,
-          themeData: event.target.getAttribute("data-browsertheme"),
-        });
-        break;
-      }
-      case "PreviewBrowserTheme": {
-        mm.sendAsyncMessage("LightWeightThemeWebInstaller:Preview", {
-          baseURI: event.target.baseURI,
-          principal: event.target.nodePrincipal,
-          themeData: event.target.getAttribute("data-browsertheme"),
-        });
-        this._previewWindow = event.target.ownerGlobal;
-        this._previewWindow.addEventListener("pagehide", this, true);
-        break;
-      }
-      case "pagehide": {
-        mm.sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview");
-        this._resetPreviewWindow();
-        break;
-      }
-      case "ResetBrowserThemePreview": {
-        if (this._previewWindow) {
-          mm.sendAsyncMessage("LightWeightThemeWebInstaller:ResetPreview",
-                           {principal: event.target.nodePrincipal});
-          this._resetPreviewWindow();
-        }
-        break;
-      }
-    }
-  }
-
-  _resetPreviewWindow() {
-    this._previewWindow.removeEventListener("pagehide", this, true);
-    this._previewWindow = null;
-  }
-}
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -5,19 +5,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("LightweightThemeChild.jsm"):
     BUG_COMPONENT = ("WebExtensions", "Themes")
 
-with Files("LightWeightThemeInstallChild.jsm"):
-    BUG_COMPONENT = ("Firefox", "Theme")
-
 with Files("PageInfoChild.jsm"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
 with Files("PageStyleChild.jsm"):
     BUG_COMPONENT = ("Firefox", "Menus")
 
 with Files("PluginChild.jsm"):
     BUG_COMPONENT = ("Core", "Plug-ins")
@@ -30,17 +27,16 @@ FINAL_TARGET_FILES.actors += [
     'BlockedSiteChild.jsm',
     'BrowserTabChild.jsm',
     'ClickHandlerChild.jsm',
     'ContentSearchChild.jsm',
     'ContextMenuChild.jsm',
     'DOMFullscreenChild.jsm',
     'FormValidationChild.jsm',
     'LightweightThemeChild.jsm',
-    'LightWeightThemeInstallChild.jsm',
     'LinkHandlerChild.jsm',
     'NetErrorChild.jsm',
     'OfflineAppsChild.jsm',
     'PageInfoChild.jsm',
     'PageStyleChild.jsm',
     'PluginChild.jsm',
     'RFPHelperChild.jsm',
     'SearchTelemetryChild.jsm',
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -179,17 +179,16 @@ pref("extensions.update.url", "https://v
 pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.interval", 86400);  // Check for updates to Extensions and
                                             // Themes every day
 
 pref("extensions.webextensions.themes.icons.buttons", "back,forward,reload,stop,bookmark_star,bookmark_menu,downloads,home,app_menu,cut,copy,paste,new_window,new_private_window,save_page,print,history,full_screen,find,options,addons,developer,synced_tabs,open_file,sidebars,share_page,subscribe,text_encoding,email_link,forget,pocket");
 
 pref("lightweightThemes.update.enabled", true);
 pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
-pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#834d29\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"}]");
 
 #if defined(MOZ_WIDEVINE_EME)
 pref("browser.eme.ui.enabled", true);
 #else
 pref("browser.eme.ui.enabled", false);
 #endif
 
 // UI tour experience.
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -666,161 +666,8 @@ var gExtensionsNotifications = {
         // removed immediately while processing this event, and PanelUI is
         // unable to identify which panel should be closed automatically.
         PanelUI.hide();
         ExtensionsUI.showSideloaded(gBrowser, addon);
       });
     }
   },
 };
-
-var LightWeightThemeWebInstaller = {
-  init() {
-    let mm = window.messageManager;
-    mm.addMessageListener("LightWeightThemeWebInstaller:Install", this);
-    mm.addMessageListener("LightWeightThemeWebInstaller:Preview", this);
-    mm.addMessageListener("LightWeightThemeWebInstaller:ResetPreview", this);
-
-    XPCOMUtils.defineLazyPreferenceGetter(this, "_apiTesting", "extensions.webapi.testing", false);
-  },
-
-  receiveMessage(message) {
-    // ignore requests from background tabs
-    if (message.target != gBrowser.selectedBrowser) {
-      return;
-    }
-
-    let data = message.data;
-
-    switch (message.name) {
-      case "LightWeightThemeWebInstaller:Install": {
-        this._installRequest(data.themeData, data.principal, data.baseURI);
-        break;
-      }
-      case "LightWeightThemeWebInstaller:Preview": {
-        this._preview(data.themeData, data.principal, data.baseURI);
-        break;
-      }
-      case "LightWeightThemeWebInstaller:ResetPreview": {
-        this._resetPreview(data && data.principal);
-        break;
-      }
-    }
-  },
-
-  handleEvent(event) {
-    switch (event.type) {
-      case "TabSelect": {
-        this._resetPreview();
-        break;
-      }
-    }
-  },
-
-  get _manager() {
-    let temp = {};
-    ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
-    delete this._manager;
-    return this._manager = temp.LightweightThemeManager;
-  },
-
-  _installRequest(dataString, principal, baseURI) {
-    // Don't allow installing off null principals.
-    if (!principal.URI) {
-      return;
-    }
-
-    let data = this._manager.parseTheme(dataString, baseURI);
-
-    if (!data) {
-      return;
-    }
-
-    // A notification bar with the option to undo is normally shown after a
-    // theme is installed.  But the discovery pane served from the url(s)
-    // below has its own toggle switch for quick undos, so don't show the
-    // notification in that case.
-    let notify = this._shouldShowUndoPrompt(principal);
-    if (this._isAllowed(principal)) {
-      this._install(data, notify);
-      return;
-    }
-
-    let strings = {
-      header: gNavigatorBundle.getFormattedString("webextPerms.header", ["<>"]),
-      addonName: data.name,
-      text: gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message2",
-                                                [principal.URI.host]),
-      acceptText: gNavigatorBundle.getString("lwthemeInstallRequest.allowButton2"),
-      acceptKey: gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey2"),
-      cancelText: gNavigatorBundle.getString("webextPerms.cancel.label"),
-      cancelKey: gNavigatorBundle.getString("webextPerms.cancel.accessKey"),
-      msgs: [],
-    };
-    ExtensionsUI.showPermissionsPrompt(gBrowser.selectedBrowser, strings, null,
-      "installWeb").then(answer => {
-      if (answer) {
-        LightWeightThemeWebInstaller._install(data, notify);
-      }
-    });
-  },
-
-  _install(newLWTheme, notify) {
-    let listener = {
-      onEnabled(aAddon) {
-        if (notify) {
-          ExtensionsUI.showInstallNotification(gBrowser.selectedBrowser, newLWTheme);
-        }
-      },
-    };
-
-    AddonManager.addAddonListener(listener);
-    this._manager.currentTheme = newLWTheme;
-    AddonManager.removeAddonListener(listener);
-  },
-
-  _preview(dataString, principal, baseURI) {
-    if (!this._isAllowed(principal))
-      return;
-
-    let data = this._manager.parseTheme(dataString, baseURI);
-    if (!data)
-      return;
-
-    this._resetPreview();
-    gBrowser.tabContainer.addEventListener("TabSelect", this);
-    this._manager.previewTheme(data);
-  },
-
-  _resetPreview(principal) {
-    if (!this._isAllowed(principal))
-      return;
-    gBrowser.tabContainer.removeEventListener("TabSelect", this);
-    this._manager.resetPreview();
-  },
-
-  _isAllowed(principal) {
-    if (!principal || !principal.URI || !principal.URI.schemeIs("https")) {
-      return false;
-    }
-
-    let pm = Services.perms;
-    return pm.testPermission(principal.URI, "install") == pm.ALLOW_ACTION;
-  },
-
-  _shouldShowUndoPrompt(principal) {
-    if (!principal || !principal.URI) {
-      return true;
-    }
-
-    let prePath = principal.URI.prePath;
-    if (prePath == "https://discovery.addons.mozilla.org") {
-      return false;
-    }
-
-    if (this._apiTesting && (prePath == "https://discovery.addons.allizom.org" ||
-                             prePath == "https://discovery.addons-dev.allizom.org")) {
-      return false;
-    }
-    return true;
-  },
-
-};
--- a/browser/base/content/browser-compacttheme.js
+++ b/browser/base/content/browser-compacttheme.js
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+ChromeUtils.defineModuleGetter(
+    this, "LightweightThemeManager",
+    "resource://gre/modules/LightweightThemeManager.jsm");
+
 /**
  * Enables compacttheme.css when needed.
  */
 var CompactTheme = {
   get styleSheet() {
     delete this.styleSheet;
     for (let styleSheet of document.styleSheets) {
       if (styleSheet.href == "chrome://browser/skin/compacttheme.css") {
rename from browser/base/content/browser-webrender.js
rename to browser/base/content/browser-graphics-utils.js
--- a/browser/base/content/browser-webrender.js
+++ b/browser/base/content/browser-graphics-utils.js
@@ -1,18 +1,26 @@
 /* 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/. */
 
 // This file is loaded into the browser window scope.
 /* eslint-env mozilla/browser-window */
 
 /**
- * Global browser interface with the WebRender backend.
+ * Global browser interface with graphics utilities.
  */
-var gWebRender = {
+var gGfxUtils = {
+  _isRecording: false,
+  /**
+   * Toggle composition recording for the current window.
+   */
+  toggleWindowRecording() {
+    window.windowUtils.setCompositionRecording(!this._isRecording);
+    this._isRecording = !this._isRecording;
+  },
   /**
    * Trigger a WebRender capture of the current state into a local folder.
    */
-  capture() {
+  webrenderCapture() {
     window.windowUtils.wrCapture();
   },
 };
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -91,17 +91,18 @@
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:Sanitize" oncommand="Sanitizer.showUI(window);"/>
     <command id="Tools:PrivateBrowsing"
       oncommand="OpenBrowserWindow({private: true});"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
 
 #ifdef NIGHTLY_BUILD
-    <command id="wrCaptureCmd" oncommand="gWebRender.capture();"/>
+    <command id="wrCaptureCmd" oncommand="gGfxUtils.webrenderCapture();"/>
+    <command id="windowRecordingCmd" oncommand="gGfxUtils.toggleWindowRecording();"/>
 #endif
 #ifdef XP_MACOSX
     <command id="minimizeWindow"
              label="&minimizeWindow.label;"
              oncommand="window.minimize();" />
     <command id="zoomWindow"
              label="&zoomWindow.label;"
              oncommand="zoomWindow();" />
@@ -312,16 +313,23 @@
 #ifdef NIGHTLY_BUILD
     <key id="key_wrCaptureCmd"
 #ifdef XP_MACOSX
     key="3" modifiers="control,shift"
 #else
     key="#" modifiers="control"
 #endif
     command="wrCaptureCmd"/>
+    <key id="key_windowRecordingCmd"
+#ifdef XP_MACOSX
+    key="4" modifiers="control,shift"
+#else
+    key="$" modifiers="control"
+#endif
+    command="windowRecordingCmd"/>
 #endif
 #ifdef XP_MACOSX
     <key id="key_minimizeWindow"
          command="minimizeWindow"
          key="&minimizeWindow.key;"
          modifiers="accel"/>
     <key id="key_openHelpMac"
          oncommand="openHelpLink('firefox-osxkey');"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -26,17 +26,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Deprecated: "resource://gre/modules/Deprecated.jsm",
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   FormValidationHandler: "resource:///modules/FormValidationHandler.jsm",
   HomePage: "resource:///modules/HomePage.jsm",
   LightweightThemeConsumer: "resource://gre/modules/LightweightThemeConsumer.jsm",
-  LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   Log: "resource://gre/modules/Log.jsm",
   LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
   MigrationUtils: "resource:///modules/MigrationUtils.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
@@ -97,18 +96,17 @@ XPCOMUtils.defineLazyScriptGetter(this, 
 XPCOMUtils.defineLazyScriptGetter(this, "FullZoom",
                                   "chrome://browser/content/browser-fullZoom.js");
 XPCOMUtils.defineLazyScriptGetter(this, "PanelUI",
                                   "chrome://browser/content/customizableui/panelUI.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gViewSourceUtils",
                                   "chrome://global/content/viewSourceUtils.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gTabsPanel",
                                   "chrome://browser/content/browser-allTabsMenu.js");
-XPCOMUtils.defineLazyScriptGetter(this, ["LightWeightThemeWebInstaller",
-                                         "gExtensionsNotifications",
+XPCOMUtils.defineLazyScriptGetter(this, ["gExtensionsNotifications",
                                          "gXPInstallObserver"],
                                   "chrome://browser/content/browser-addons.js");
 XPCOMUtils.defineLazyScriptGetter(this, "ctrlTab",
                                   "chrome://browser/content/browser-ctrlTab.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["CustomizationHandler", "AutoHideMenubar"],
                                   "chrome://browser/content/browser-customization.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["PointerLock", "FullScreen"],
                                   "chrome://browser/content/browser-fullScreenAndPointerLock.js");
@@ -136,18 +134,18 @@ XPCOMUtils.defineLazyScriptGetter(this, 
 XPCOMUtils.defineLazyScriptGetter(this, ["DownloadsButton",
                                          "DownloadsIndicatorView"],
                                   "chrome://browser/content/downloads/indicator.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gEditItemOverlay",
                                   "chrome://browser/content/places/editBookmark.js");
 XPCOMUtils.defineLazyScriptGetter(this, "SearchOneOffs",
                                   "chrome://browser/content/search/search-one-offs.js");
 if (AppConstants.NIGHTLY_BUILD) {
-  XPCOMUtils.defineLazyScriptGetter(this, "gWebRender",
-                                    "chrome://browser/content/browser-webrender.js");
+  XPCOMUtils.defineLazyScriptGetter(this, "gGfxUtils",
+                                    "chrome://browser/content/browser-graphics-utils.js");
 }
 
 XPCOMUtils.defineLazyScriptGetter(this, "pktUI", "chrome://pocket/content/main.js");
 XPCOMUtils.defineLazyScriptGetter(this, "ToolbarKeyboardNavigator",
   "chrome://browser/content/browser-toolbarKeyNav.js");
 
 // lazy service getters
 
@@ -1649,18 +1647,16 @@ var gBrowserInit = {
 
     if (AppConstants.platform != "macosx") {
       updateEditUIVisibility();
       let placesContext = document.getElementById("placesContext");
       placesContext.addEventListener("popupshowing", updateEditUIVisibility);
       placesContext.addEventListener("popuphiding", updateEditUIVisibility);
     }
 
-    LightWeightThemeWebInstaller.init();
-
     FullScreen.init();
     PointerLock.init();
 
     if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
       MenuTouchModeObserver.init();
     }
 
     if (AppConstants.MOZ_DATA_REPORTING)
--- a/browser/base/content/test/general/browser_compacttheme.js
+++ b/browser/base/content/test/general/browser_compacttheme.js
@@ -1,91 +1,48 @@
 /*
  * Testing changes for compact themes.
  * A special stylesheet should be added to the browser.xul document
  * when the firefox-compact-light and firefox-compact-dark lightweight
  * themes are applied.
  */
 
+const DEFAULT_THEME = "default-theme@mozilla.org";
 const PREF_LWTHEME_USED_THEMES = "lightweightThemes.usedThemes";
 const COMPACT_LIGHT_ID = "firefox-compact-light@mozilla.org";
 const COMPACT_DARK_ID = "firefox-compact-dark@mozilla.org";
-const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
+const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+
+async function selectTheme(id) {
+  let theme = await AddonManager.getAddonByID(id || DEFAULT_THEME);
+  await theme.enable();
+}
 
 registerCleanupFunction(() => {
   // Set preferences back to their original values
-  LightweightThemeManager.currentTheme = null;
-  Services.prefs.clearUserPref(PREF_LWTHEME_USED_THEMES);
+  return selectTheme(null);
 });
 
 function tick() {
   return new Promise(SimpleTest.executeSoon);
 }
 
 add_task(async function startTests() {
   info("Setting the current theme to null");
-  LightweightThemeManager.currentTheme = null;
+  await selectTheme(null);
   await tick();
   ok(!CompactTheme.isStyleSheetEnabled, "There is no compact style sheet when no lw theme is applied.");
 
-  info("Adding a lightweight theme.");
-  LightweightThemeManager.currentTheme = dummyLightweightTheme("preview0");
-  await tick();
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been removed when a lightweight theme is applied.");
-
   info("Applying the dark compact theme.");
-  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(COMPACT_DARK_ID);
+  await selectTheme(COMPACT_DARK_ID);
   await tick();
   ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been added when the compact lightweight theme is applied");
 
   info("Applying the light compact theme.");
-  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(COMPACT_LIGHT_ID);
+  await selectTheme(COMPACT_LIGHT_ID);
   await tick();
   ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been added when the compact lightweight theme is applied");
 
-  info("Adding a different lightweight theme.");
-  LightweightThemeManager.currentTheme = dummyLightweightTheme("preview1");
-  await tick();
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet has been removed when a lightweight theme is applied.");
-
   info("Unapplying all themes.");
-  LightweightThemeManager.currentTheme = null;
+  await selectTheme(null);
   await tick();
   ok(!CompactTheme.isStyleSheetEnabled, "There is no compact style sheet when no lw theme is applied.");
 });
-
-function dummyLightweightTheme(id) {
-  return {
-    id,
-    name: id,
-    iconURL: "resource:///chrome/browser/content/browser/defaultthemes/light.icon.svg",
-    textcolor: "red",
-    accentcolor: "blue",
-  };
-}
-
-add_task(async function testLightweightThemePreview() {
-  info("Setting compact to current and previewing others");
-  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(COMPACT_LIGHT_ID);
-  await tick();
-  ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled.");
-  LightweightThemeManager.previewTheme(dummyLightweightTheme("preview0"));
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is not enabled after a lightweight theme preview.");
-  LightweightThemeManager.resetPreview();
-  LightweightThemeManager.previewTheme(dummyLightweightTheme("preview1"));
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is not enabled after a second lightweight theme preview.");
-  LightweightThemeManager.resetPreview();
-  ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled again after resetting the preview.");
-  LightweightThemeManager.currentTheme = null;
-  await tick();
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is gone after removing the current theme.");
-
-  info("Previewing the compact theme");
-  LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme(COMPACT_LIGHT_ID));
-  ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled.");
-  LightweightThemeManager.previewTheme(LightweightThemeManager.getUsedTheme(COMPACT_DARK_ID));
-  ok(CompactTheme.isStyleSheetEnabled, "The compact stylesheet is enabled.");
-
-  LightweightThemeManager.previewTheme(dummyLightweightTheme("preview2"));
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is now disabled after previewing a new sheet.");
-  LightweightThemeManager.resetPreview();
-  ok(!CompactTheme.isStyleSheetEnabled, "The compact stylesheet is now disabled after resetting the preview.");
-});
--- a/browser/base/content/test/tabs/browser_newwindow_tabstrip_overflow.js
+++ b/browser/base/content/test/tabs/browser_newwindow_tabstrip_overflow.js
@@ -1,25 +1,32 @@
 /* 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 {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
+const DEFAULT_THEME = "default-theme@mozilla.org";
+
+const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+
+async function selectTheme(id) {
+  let theme = await AddonManager.getAddonByID(id || DEFAULT_THEME);
+  await theme.enable();
+}
+
 registerCleanupFunction(() => {
-  LightweightThemeManager.currentTheme = null;
-  Services.prefs.clearUserPref("lightweightThemes.usedThemes");
+  return selectTheme(null);
 });
 
 add_task(async function withoutLWT() {
   let win = await BrowserTestUtils.openNewBrowserWindow();
   ok(!win.gBrowser.tabContainer.hasAttribute("overflow"), "tab container not overflowing");
   ok(win.gBrowser.tabContainer.arrowScrollbox.hasAttribute("notoverflowing"), "arrow scrollbox not overflowing");
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function withLWT() {
-  LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-compact-light@mozilla.org");
+  await selectTheme("firefox-compact-light@mozilla.org");
   let win = await BrowserTestUtils.openNewBrowserWindow();
   ok(!win.gBrowser.tabContainer.hasAttribute("overflow"), "tab container not overflowing");
   ok(win.gBrowser.tabContainer.arrowScrollbox.hasAttribute("notoverflowing"), "arrow scrollbox not overflowing");
   await BrowserTestUtils.closeWindow(win);
 });
 
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -53,17 +53,17 @@ browser.jar:
         content/browser/browser-plugins.js            (content/browser-plugins.js)
         content/browser/browser-safebrowsing.js       (content/browser-safebrowsing.js)
         content/browser/browser-sidebar.js            (content/browser-sidebar.js)
         content/browser/browser-siteIdentity.js       (content/browser-siteIdentity.js)
         content/browser/browser-sync.js               (content/browser-sync.js)
         content/browser/browser-tabsintitlebar.js     (content/browser-tabsintitlebar.js)
         content/browser/browser-toolbarKeyNav.js      (content/browser-toolbarKeyNav.js)
         content/browser/browser-thumbnails.js         (content/browser-thumbnails.js)
-        content/browser/browser-webrender.js          (content/browser-webrender.js)
+        content/browser/browser-graphics-utils.js     (content/browser-graphics-utils.js)
         content/browser/tab-content.js                (content/tab-content.js)
         content/browser/content.js                    (content/content.js)
         content/browser/defaultthemes/1.header.jpg    (content/defaultthemes/1.header.jpg)
         content/browser/defaultthemes/1.icon.jpg      (content/defaultthemes/1.icon.jpg)
         content/browser/defaultthemes/1.preview.jpg   (content/defaultthemes/1.preview.jpg)
         content/browser/defaultthemes/2.header.jpg    (content/defaultthemes/2.header.jpg)
         content/browser/defaultthemes/2.icon.jpg      (content/defaultthemes/2.icon.jpg)
         content/browser/defaultthemes/2.preview.jpg   (content/defaultthemes/2.preview.jpg)
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -127,27 +127,16 @@ let ACTORS = {
     child: {
       module: "resource:///actors/FormValidationChild.jsm",
       events: {
         "MozInvalidForm": {},
       },
     },
   },
 
-  LightWeightThemeInstall: {
-    child: {
-      module: "resource:///actors/LightWeightThemeInstallChild.jsm",
-      events: {
-        "InstallBrowserTheme": {wantUntrusted: true},
-        "PreviewBrowserTheme": {wantUntrusted: true},
-        "ResetBrowserThemePreview": {wantUntrusted: true},
-      },
-    },
-  },
-
   LightweightTheme: {
     child: {
       module: "resource:///actors/LightweightThemeChild.jsm",
       matches: ["about:home", "about:newtab", "about:welcome"],
       events: {
         "pageshow": {mozSystemGroup: true},
       },
     },
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -5,46 +5,52 @@
 
 var EXPORTED_SYMBOLS = ["CustomizableUI"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  AddonManager: "resource://gre/modules/AddonManager.jsm",
+  AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
   SearchWidgetTracker: "resource:///modules/SearchWidgetTracker.jsm",
   CustomizableWidgets: "resource:///modules/CustomizableWidgets.jsm",
   DeferredTask: "resource://gre/modules/DeferredTask.jsm",
   PanelMultiView: "resource:///modules/PanelMultiView.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
-  LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
   const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
   return Services.strings.createBundle(kUrl);
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "gELS",
   "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
 
+const kDefaultThemeID = "default-theme@mozilla.org";
+
 const kSpecialWidgetPfx = "customizableui-special-";
 
 const kPrefCustomizationState        = "browser.uiCustomization.state";
 const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
 const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
 const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
 const kPrefExtraDragSpace            = "browser.tabs.extraDragSpace";
 const kPrefUIDensity                 = "browser.uidensity";
 const kPrefAutoTouchMode             = "browser.touchmode.auto";
 const kPrefAutoHideDownloadsButton   = "browser.download.autohideButton";
 
 const kExpectedWindowURL = AppConstants.BROWSER_CHROME_URL;
 
+var gDefaultTheme;
+var gSelectedTheme;
+
 /**
  * The keys are the handlers that are fired when the event type (the value)
  * is fired on the subview. A widget that provides a subview has the option
  * of providing onViewShowing and onViewHiding event handlers.
  */
 const kSubviewEvents = [
   "ViewShowing",
   "ViewHiding",
@@ -173,16 +179,21 @@ XPCOMUtils.defineLazyGetter(this, "log",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
 var CustomizableUIInternal = {
   initialize() {
     log.debug("Initializing");
 
+    Services.obs.addObserver(this, "xpi-database-loaded");
+    if (AddonManagerPrivate.isDBLoaded()) {
+      this.observe(null, "xpi-database-loaded");
+    }
+
     this.addListener(this);
     this._defineBuiltInWidgets();
     this.loadSavedState();
     this._updateForNewVersion();
     this._markObsoleteBuiltinButtonsSeen();
 
     this.registerArea(CustomizableUI.AREA_FIXED_OVERFLOW_PANEL, {
       type: CustomizableUI.TYPE_MENU_PANEL,
@@ -240,16 +251,32 @@ var CustomizableUIInternal = {
         "personal-bookmarks",
       ],
       defaultCollapsed: true,
     }, true);
 
     SearchWidgetTracker.init();
   },
 
+  async observe(subject, topic, data) {
+    if (topic == "xpi-database-loaded") {
+      AddonManager.addAddonListener(this);
+
+      let addons = await AddonManager.getAddonsByTypes(["theme"]);
+      gDefaultTheme = addons.find(addon => addon.id == kDefaultThemeID);
+      gSelectedTheme = addons.find(addon => addon.isActive) || gDefaultTheme;
+    }
+  },
+
+  onEnabled(addon) {
+    if (addon.type == "theme") {
+      gSelectedTheme = addon;
+    }
+  },
+
   get _builtinAreas() {
     return new Set([
       ...this._builtinToolbars,
       CustomizableUI.AREA_FIXED_OVERFLOW_PANEL,
     ]);
   },
 
   get _builtinToolbars() {
@@ -2647,28 +2674,28 @@ var CustomizableUIInternal = {
       // kPrefDrawInTitlebar may not be defined on Linux/Gtk+ which throws an exception
       // and leads to whole test failure. Let's set a fallback default value to avoid that,
       // both titlebar states are tested anyway and it's not important which state is tested first.
       gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar, false);
       gUIStateBeforeReset.extraDragSpace = Services.prefs.getBoolPref(kPrefExtraDragSpace);
       gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
       gUIStateBeforeReset.uiDensity = Services.prefs.getIntPref(kPrefUIDensity);
       gUIStateBeforeReset.autoTouchMode = Services.prefs.getBoolPref(kPrefAutoTouchMode);
-      gUIStateBeforeReset.currentTheme = LightweightThemeManager.currentTheme;
+      gUIStateBeforeReset.currentTheme = gSelectedTheme;
       gUIStateBeforeReset.autoHideDownloadsButton = Services.prefs.getBoolPref(kPrefAutoHideDownloadsButton);
       gUIStateBeforeReset.newElementCount = gNewElementCount;
     } catch (e) { }
 
     Services.prefs.clearUserPref(kPrefCustomizationState);
     Services.prefs.clearUserPref(kPrefDrawInTitlebar);
     Services.prefs.clearUserPref(kPrefExtraDragSpace);
     Services.prefs.clearUserPref(kPrefUIDensity);
     Services.prefs.clearUserPref(kPrefAutoTouchMode);
     Services.prefs.clearUserPref(kPrefAutoHideDownloadsButton);
-    LightweightThemeManager.currentTheme = null;
+    gDefaultTheme.enable();
     gNewElementCount = 0;
     log.debug("State reset");
 
     // Reset placements to make restoring default placements possible.
     gPlacements = new Map();
     gDirtyAreaCache = new Set();
     gSeenWidgets = new Set();
     // Clear the saved state to ensure that defaults will be used.
@@ -2720,17 +2747,17 @@ var CustomizableUIInternal = {
     this._clearPreviousUIState();
 
     Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
     Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
     Services.prefs.setBoolPref(kPrefExtraDragSpace, extraDragSpace);
     Services.prefs.setIntPref(kPrefUIDensity, uiDensity);
     Services.prefs.setBoolPref(kPrefAutoTouchMode, autoTouchMode);
     Services.prefs.setBoolPref(kPrefAutoHideDownloadsButton, autoHideDownloadsButton);
-    LightweightThemeManager.currentTheme = currentTheme;
+    currentTheme.enable();
     this.loadSavedState();
     // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
     // and we don't need to do anything else here:
     if (gSavedState) {
       for (let areaId of Object.keys(gSavedState.placements)) {
         let placements = gSavedState.placements[areaId];
         gPlacements.set(areaId, placements);
       }
@@ -2937,19 +2964,19 @@ var CustomizableUIInternal = {
       return false;
     }
 
     if (Services.prefs.prefHasUserValue(kPrefExtraDragSpace)) {
       log.debug(kPrefExtraDragSpace + " pref is non-default");
       return false;
     }
 
-    if (LightweightThemeManager.currentTheme &&
-        LightweightThemeManager.currentTheme.id != "default-theme@mozilla.org") {
-      log.debug(LightweightThemeManager.currentTheme + " theme is non-default");
+    // This should just be `gDefaultTheme.isActive`, but bugs...
+    if (gDefaultTheme && gDefaultTheme.id != gSelectedTheme.id) {
+      log.debug(gSelectedTheme.id + " theme is non-default");
       return false;
     }
 
     return true;
   },
 
   setToolbarVisibility(aToolbarId, aIsVisible) {
     // We only persist the attribute the first time.
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -23,24 +23,24 @@ const kDownloadAutoHidePref = "browser.d
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {CustomizableUI} = ChromeUtils.import("resource:///modules/CustomizableUI.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["CSS"]);
 
+ChromeUtils.defineModuleGetter(this, "AddonManager",
+                               "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AMTelemetry",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "DragPositionManager",
                                "resource:///modules/DragPositionManager.jsm");
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
                                "resource://gre/modules/BrowserUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
-                               "resource://gre/modules/LightweightThemeManager.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionStore",
                                "resource:///modules/sessionstore/SessionStore.jsm");
 XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
   const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
   return Services.strings.createBundle(kUrl);
 });
 XPCOMUtils.defineLazyPreferenceGetter(this, "gCosmeticAnimationsEnabled",
                                       "toolkit.cosmeticAnimations.enabled");
@@ -159,21 +159,21 @@ CustomizeMode.prototype = {
     }
     if (this._customizing) {
       this.exit();
     } else {
       this.enter();
     }
   },
 
-  _updateLWThemeButtonIcon() {
+  async _updateThemeButtonIcon() {
     let lwthemeButton = this.$("customization-lwtheme-button");
     let lwthemeIcon = this.document.getAnonymousElementByAttribute(lwthemeButton,
                         "class", "button-icon");
-    let theme = LightweightThemeManager.currentTheme;
+    let theme = (await AddonManager.getAddonsByTypes(["theme"])).find(addon => addon.isActive);
     lwthemeIcon.style.backgroundImage = theme ? "url(" + theme.iconURL + ")" : "";
   },
 
   setTab(aTab) {
     if (gTab == aTab) {
       return;
     }
 
@@ -339,18 +339,18 @@ CustomizeMode.prototype = {
       window.setTimeout(() => {
         // Force layout reflow to ensure the animation runs,
         // and make it async so it doesn't affect the timing.
         this.visiblePalette.clientTop;
         this.visiblePalette.setAttribute("showing", "true");
       }, 0);
       this._updateEmptyPaletteNotice();
 
-      this._updateLWThemeButtonIcon();
-      Services.obs.addObserver(this, "lightweight-theme-changed");
+      this._updateThemeButtonIcon();
+      AddonManager.addAddonListener(this);
 
       this._setupDownloadAutoHideToggle();
 
       this._handler.isEnteringCustomizeMode = false;
 
       CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
 
       if (!this._wantToBeInCustomizeMode) {
@@ -383,17 +383,17 @@ CustomizeMode.prototype = {
                 "We'll exit after resetting has finished.");
       return;
     }
 
     this._handler.isExitingCustomizeMode = true;
 
     this._teardownDownloadAutoHideToggle();
 
-    Services.obs.removeObserver(this, "lightweight-theme-changed");
+    AddonManager.removeAddonListener(this);
     CustomizableUI.removeListener(this);
 
     this.document.removeEventListener("keypress", this);
 
     let window = this.window;
     let document = this.document;
 
     this.togglePong(false);
@@ -1305,33 +1305,23 @@ CustomizeMode.prototype = {
   updateAutoTouchMode(checked) {
     Services.prefs.setBoolPref("browser.touchmode.auto", checked);
     // Re-render the menu items since the active mode might have
     // change because of this.
     this.onUIDensityMenuShowing();
     this._onUIChange();
   },
 
-  onLWThemesMenuShowing(aEvent) {
+  async onThemesMenuShowing(aEvent) {
     const DEFAULT_THEME_ID = "default-theme@mozilla.org";
     const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
     const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
     const MAX_THEME_COUNT = 6;
 
-    this._clearLWThemesMenu(aEvent.target);
-
-    function previewTheme(aPreviewThemeEvent) {
-      LightweightThemeManager.previewTheme(
-        aPreviewThemeEvent.target.theme.id != DEFAULT_THEME_ID ?
-        aPreviewThemeEvent.target.theme : null);
-    }
-
-    function resetPreview() {
-      LightweightThemeManager.resetPreview();
-    }
+    this._clearThemesMenu(aEvent.target);
 
     let onThemeSelected = panel => {
       // This causes us to call _onUIChange when the LWT actually changes,
       // so the restore defaults / undo reset button is updated correctly.
       this._nextThemeChangeUserTriggered = true;
       panel.hidePopup();
     };
 
@@ -1341,130 +1331,63 @@ CustomizeMode.prototype = {
       let tbb = doc.createXULElement("toolbarbutton");
       tbb.theme = aTheme;
       tbb.setAttribute("label", aTheme.name);
       tbb.setAttribute("image", aTheme.iconURL);
       if (aTheme.description)
         tbb.setAttribute("tooltiptext", aTheme.description);
       tbb.setAttribute("tabindex", "0");
       tbb.classList.add("customization-lwtheme-menu-theme");
-      let isActive = activeThemeID == aTheme.id;
+      let isActive = aTheme.isActive;
       tbb.setAttribute("aria-checked", isActive);
       tbb.setAttribute("role", "menuitemradio");
       if (isActive) {
         tbb.setAttribute("active", "true");
       }
-      tbb.addEventListener("focus", previewTheme);
-      tbb.addEventListener("mouseover", previewTheme);
-      tbb.addEventListener("blur", resetPreview);
 
       return tbb;
     }
 
-    let themes = [];
-    let lwts = LightweightThemeManager.usedThemes;
-    let currentLwt = LightweightThemeManager.currentTheme;
-
-    let activeThemeID = currentLwt ? currentLwt.id : DEFAULT_THEME_ID;
+    let themes = await AddonManager.getAddonsByTypes(["theme"]);
+    let currentTheme = themes.find(theme => theme.isActive);
 
     // Move the current theme (if any) and the light/dark themes to the start:
-    let importantThemes = [DEFAULT_THEME_ID, LIGHT_THEME_ID, DARK_THEME_ID];
-    if (currentLwt && !importantThemes.includes(currentLwt.id)) {
-      importantThemes.push(currentLwt.id);
+    let importantThemes = new Set([DEFAULT_THEME_ID, LIGHT_THEME_ID, DARK_THEME_ID]);
+    if (currentTheme) {
+      importantThemes.add(currentTheme.id);
     }
-    for (let importantTheme of importantThemes) {
-      let themeIndex = lwts.findIndex(theme => theme.id == importantTheme);
-      if (themeIndex > -1) {
-        themes.push(...lwts.splice(themeIndex, 1));
-      }
-    }
-    themes = themes.concat(lwts);
+
+    themes.sort((a, b) => importantThemes.has(b) - importantThemes.has(a));
+
     if (themes.length > MAX_THEME_COUNT)
       themes.length = MAX_THEME_COUNT;
 
     let footer = doc.getElementById("customization-lwtheme-menu-footer");
     let panel = footer.parentNode;
-    let recommendedLabel = doc.getElementById("customization-lwtheme-menu-recommended");
     for (let theme of themes) {
       let button = buildToolbarButton(theme);
-      button.addEventListener("command", () => {
-        if ("userDisabled" in button.theme)
-          button.theme.userDisabled = false;
-        else
-          LightweightThemeManager.currentTheme = button.theme;
+      button.addEventListener("command", async () => {
+        await button.theme.enable();
         onThemeSelected(panel);
         AMTelemetry.recordActionEvent({
           object: "customize",
           action: "enable",
           extra: {type: "theme", addonId: theme.id},
         });
       });
-      panel.insertBefore(button, recommendedLabel);
-    }
-
-    function panelMouseOut(e) {
-      // mouseout events bubble, so we get mouseout events for the buttons
-      // in the panel. Here, we only care when the mouse actually leaves
-      // the panel. For some reason event.target might not be the panel
-      // even when the mouse *is* leaving the panel, so we check
-      // explicitOriginalTarget instead.
-      if (e.explicitOriginalTarget == panel) {
-        resetPreview();
-      }
-    }
-
-    panel.addEventListener("mouseout", panelMouseOut);
-    panel.addEventListener("popuphidden", () => {
-      panel.removeEventListener("mouseout", panelMouseOut);
-      resetPreview();
-    }, {once: true});
-
-    let lwthemePrefs = Services.prefs.getBranch("lightweightThemes.");
-    let recommendedThemes = lwthemePrefs.getStringPref("recommendedThemes");
-    recommendedThemes = JSON.parse(recommendedThemes);
-    let sb = Services.strings.createBundle("chrome://browser/locale/lightweightThemes.properties");
-    for (let theme of recommendedThemes) {
-      try {
-        theme.name = sb.GetStringFromName("lightweightThemes." + theme.id + ".name");
-        theme.description = sb.GetStringFromName("lightweightThemes." + theme.id + ".description");
-      } catch (ex) {
-        // If finding strings for this failed, just don't build it. This can
-        // happen for users with 'older' recommended themes lists, some of which
-        // have since been removed from Firefox.
-        continue;
-      }
-      let button = buildToolbarButton(theme);
-      button.addEventListener("command", () => {
-        LightweightThemeManager.setLocalTheme(button.theme);
-        recommendedThemes = recommendedThemes.filter((aTheme) => { return aTheme.id != button.theme.id; });
-        lwthemePrefs.setStringPref("recommendedThemes",
-                                   JSON.stringify(recommendedThemes));
-        onThemeSelected(panel);
-        let addonId = `${button.theme.id}@personas.mozilla.org`;
-        AMTelemetry.recordActionEvent({
-          object: "customize",
-          action: "enable",
-          value: "recommended",
-          extra: {type: "theme", addonId},
-        });
-      });
       panel.insertBefore(button, footer);
     }
-    let hideRecommendedLabel = (footer.previousElementSibling == recommendedLabel);
-    recommendedLabel.hidden = hideRecommendedLabel;
   },
 
-  _clearLWThemesMenu(panel) {
+  _clearThemesMenu(panel) {
     let footer = this.$("customization-lwtheme-menu-footer");
-    let recommendedLabel = this.$("customization-lwtheme-menu-recommended");
-    for (let element of [footer, recommendedLabel]) {
-      while (element.previousElementSibling &&
-             element.previousElementSibling.localName == "toolbarbutton") {
-        element.previousElementSibling.remove();
-      }
+    let element = footer;
+    while (element.previousElementSibling &&
+           element.previousElementSibling.localName == "toolbarbutton") {
+      element.previousElementSibling.remove();
     }
 
     // Workaround for bug 1059934
     panel.removeAttribute("height");
   },
 
   _onUIChange() {
     this._changed = true;
@@ -1577,24 +1500,29 @@ CustomizeMode.prototype = {
       case "nsPref:changed":
         this._updateResetButton();
         this._updateUndoResetButton();
         if (this._canDrawInTitlebar()) {
           this._updateTitlebarCheckbox();
           this._updateDragSpaceCheckbox();
         }
         break;
-      case "lightweight-theme-changed":
-        this._updateLWThemeButtonIcon();
-        if (this._nextThemeChangeUserTriggered) {
-          this._onUIChange();
-        }
-        this._nextThemeChangeUserTriggered = false;
-        break;
+    }
+  },
+
+  async onEnabled(addon) {
+    if (addon.type != "theme") {
+      return;
     }
+
+    await this._updateThemeButtonIcon();
+    if (this._nextThemeChangeUserTriggered) {
+      this._onUIChange();
+    }
+    this._nextThemeChangeUserTriggered = false;
   },
 
   _canDrawInTitlebar() {
     return this.window.TabsInTitlebar.systemSupported;
   },
 
   _updateTitlebarCheckbox() {
     let drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref,
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -36,22 +36,21 @@
     <checkbox id="customization-extra-drag-space-checkbox" class="customizationmode-checkbox"
               label="&customizeMode.extraDragSpace;"
               oncommand="gCustomizeMode.toggleDragSpace(this.checked)"/>
     <button id="customization-toolbar-visibility-button" label="&customizeMode.toolbars2;" class="customizationmode-button" type="menu">
       <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
     </button>
     <button id="customization-lwtheme-button" label="&customizeMode.lwthemes;" class="customizationmode-button" type="menu">
       <panel type="arrow" id="customization-lwtheme-menu"
-             onpopupshowing="gCustomizeMode.onLWThemesMenuShowing(event);"
+             onpopupshowing="gCustomizeMode.onThemesMenuShowing(event);"
              position="topcenter bottomleft"
              flip="none"
              role="menu">
         <label id="customization-lwtheme-menu-header" value="&customizeMode.lwthemes.myThemes;"/>
-        <label id="customization-lwtheme-menu-recommended" value="&customizeMode.lwthemes.recommended;"/>
         <hbox id="customization-lwtheme-menu-footer">
           <toolbarbutton class="customization-lwtheme-menu-footeritem"
                          label="&customizeMode.lwthemes.menuManage;"
                          accesskey="&customizeMode.lwthemes.menuManage.accessKey;"
                          tabindex="0"
                          oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
           <toolbarbutton class="customization-lwtheme-menu-footeritem"
                          label="&customizeMode.lwthemes.menuGetMore;"
--- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
+++ b/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
@@ -2,21 +2,19 @@
   * License, v. 2.0. If a copy of the MPL was not distributed with this
   * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const DEFAULT_THEME_ID = "default-theme@mozilla.org";
 const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
 const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
-const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
 
 add_task(async function() {
   Services.prefs.clearUserPref("lightweightThemes.usedThemes");
-  Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
 
   await startCustomizing();
   // Check restore defaults button is disabled.
   ok(document.getElementById("customization-reset-button").disabled,
      "Reset button should start out disabled");
 
   let themesButton = document.getElementById("customization-lwtheme-button");
   let themesButtonIcon = document.getAnonymousElementByAttribute(themesButton,
@@ -46,107 +44,102 @@ add_task(async function() {
   await startCustomizing();
   info("Started customizing a second time");
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
   info("Clicked on themes button a second time");
   await popupShownPromise;
 
   let header = document.getElementById("customization-lwtheme-menu-header");
-  let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
+  let footer = document.getElementById("customization-lwtheme-menu-footer");
 
-  is(header.nextElementSibling.nextElementSibling.nextElementSibling.nextElementSibling, recommendedHeader,
+  is(header.nextElementSibling.nextElementSibling.nextElementSibling.nextElementSibling, footer,
      "There should only be three themes (default, light, dark) in the 'My Themes' section by default");
   is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID,
      "The first theme should be the default theme");
   is(header.nextElementSibling.nextElementSibling.theme.id, LIGHT_THEME_ID,
      "The second theme should be the light theme");
   is(header.nextElementSibling.nextElementSibling.nextElementSibling.theme.id, DARK_THEME_ID,
      "The third theme should be the dark theme");
 
   let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
   header.nextElementSibling.nextElementSibling.doCommand(); // Select light theme
   info("Clicked on light theme");
   await themeChangedPromise;
 
+  let button = document.getElementById("customization-reset-button");
+  await TestUtils.waitForCondition(() => !button.disabled);
+
   // Check restore defaults button is enabled.
-  ok(!document.getElementById("customization-reset-button").disabled,
+  ok(!button.disabled,
      "Reset button should not be disabled anymore");
   ok((/light/i).test(themesButtonIcon.style.backgroundImage),
      `Button should show light theme thumbnail - was: "${themesButtonIcon.style.backgroundImage}"`);
 
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
   info("Clicked on themes button a third time");
   await popupShownPromise;
 
   let activeThemes = popup.querySelectorAll("toolbarbutton.customization-lwtheme-menu-theme[active]");
   is(activeThemes.length, 1, "Exactly 1 theme should be selected");
   if (activeThemes.length > 0) {
     is(activeThemes[0].theme.id, LIGHT_THEME_ID, "Light theme should be selected");
   }
 
-  let firstLWTheme = recommendedHeader.nextElementSibling;
+  let firstLWTheme = footer.previousElementSibling;
   let firstLWThemeId = firstLWTheme.theme.id;
   themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
   firstLWTheme.doCommand();
   info("Clicked on first theme");
   await themeChangedPromise;
 
+  await new Promise(executeSoon);
+
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
   info("Clicked on themes button");
   await popupShownPromise;
 
   activeThemes = popup.querySelectorAll("toolbarbutton.customization-lwtheme-menu-theme[active]");
   is(activeThemes.length, 1, "Exactly 1 theme should be selected");
   if (activeThemes.length > 0) {
     is(activeThemes[0].theme.id, firstLWThemeId, "First theme should be selected");
   }
 
   is(header.nextElementSibling.theme.id, DEFAULT_THEME_ID, "The first theme should be the Default theme");
-  let installedThemeId = header.nextElementSibling.nextElementSibling.nextElementSibling.nextElementSibling.theme.id;
-  ok(installedThemeId.startsWith(firstLWThemeId),
-     "The second theme in the 'My Themes' section should be the newly installed theme: " +
-     "Installed theme id: " + installedThemeId + "; First theme ID: " + firstLWThemeId);
   let themeCount = 0;
   let iterNode = header;
   while (iterNode.nextElementSibling && iterNode.nextElementSibling.theme) {
     themeCount++;
     iterNode = iterNode.nextElementSibling;
   }
-  is(themeCount, 4,
+  is(themeCount, 3,
      "There should be four themes in the 'My Themes' section");
 
   let defaultTheme = header.nextElementSibling;
   defaultTheme.doCommand();
   await new Promise(SimpleTest.executeSoon);
   is(Services.prefs.getCharPref("lightweightThemes.selectedThemeID"),
      DEFAULT_THEME_ID, "Default theme should be selected");
 
   // ensure current theme isn't set to "Default"
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
   info("Clicked on themes button a fourth time");
   await popupShownPromise;
 
-  firstLWTheme = recommendedHeader.nextElementSibling;
-  themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
-  firstLWTheme.doCommand();
-  info("Clicked on first theme again");
-  await themeChangedPromise;
-
   // check that "Restore Defaults" button resets theme
   await gCustomizeMode.reset();
-  is(LightweightThemeManager.currentTheme.id, DEFAULT_THEME_ID, "Current theme reset to default");
+
+  defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID);
+  is(defaultTheme.isActive, true, "Current theme reset to default");
 
   await endCustomizing();
   Services.prefs.setCharPref("lightweightThemes.usedThemes", "[]");
-  Services.prefs.setCharPref("lightweightThemes.recommendedThemes", "[]");
-  info("Removed all recommended themes");
   await startCustomizing();
   popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
   info("Clicked on themes button a fifth time");
   await popupShownPromise;
   header = document.getElementById("customization-lwtheme-menu-header");
   is(header.hidden, false, "Header should never be hidden");
   let themeNode = header.nextElementSibling;
@@ -155,23 +148,15 @@ add_task(async function() {
 
   themeNode = themeNode.nextElementSibling;
   is(themeNode.theme.id, LIGHT_THEME_ID, "The second theme should be the Light theme");
   is(themeNode.hidden, false, "The light theme should never be hidden");
 
   themeNode = themeNode.nextElementSibling;
   is(themeNode.theme.id, DARK_THEME_ID, "The third theme should be the Dark theme");
   is(themeNode.hidden, false, "The dark theme should never be hidden");
-
-  recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
-  is(themeNode.nextElementSibling, recommendedHeader,
-     "There should only be three themes (default, light, dark) in the 'My Themes' section now");
-  let footer = document.getElementById("customization-lwtheme-menu-footer");
-  is(recommendedHeader.nextElementSibling.id, footer.id, "There should be no recommended themes in the menu");
-  is(recommendedHeader.hidden, true, "The recommendedHeader should be hidden since there are no recommended themes");
 });
 
 add_task(async function asyncCleanup() {
   await endCustomizing();
 
   Services.prefs.clearUserPref("lightweightThemes.usedThemes");
-  Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
 });
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -18,35 +18,38 @@ add_task(async function() {
 
   let themesButton = document.getElementById("customization-lwtheme-button");
   let popup = document.getElementById("customization-lwtheme-menu");
   let popupShownPromise = popupShown(popup);
   EventUtils.synthesizeMouseAtCenter(themesButton, {});
   info("Clicked on themes button");
   await popupShownPromise;
 
-  let recommendedHeader = document.getElementById("customization-lwtheme-menu-recommended");
-  let firstLWTheme = recommendedHeader.nextElementSibling;
+  let header = document.getElementById("customization-lwtheme-menu-header");
+  let firstLWTheme = header.nextElementSibling.nextElementSibling;
   let firstLWThemeId = firstLWTheme.theme.id;
   let themeChangedPromise = promiseObserverNotified("lightweight-theme-changed");
   firstLWTheme.doCommand();
   info("Clicked on first theme");
   await themeChangedPromise;
 
-  is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme changed to first option");
+  let theme = await AddonManager.getAddonByID(firstLWThemeId);
+  is(theme.isActive, true, "Theme changed to first option");
 
   await gCustomizeMode.reset();
 
   ok(CustomizableUI.inDefaultState, "In default state after reset");
   is(undoResetButton.hidden, false, "The undo button is visible after reset");
-  is(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org", "Theme reset to default");
+  theme = await AddonManager.getAddonByID("default-theme@mozilla.org");
+  is(theme.isActive, true, "Theme reset to default");
 
   await gCustomizeMode.undoReset();
 
-  is(LightweightThemeManager.currentTheme.id, firstLWThemeId, "Theme has been reset from default to original choice");
+  theme = await AddonManager.getAddonByID(firstLWThemeId);
+  is(theme.isActive, true, "Theme has been reset from default to original choice");
   ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset");
   is(undoResetButton.hidden, true, "The undo button is hidden after clicking on the undo button");
   is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
 
   await gCustomizeMode.reset();
 });
 
 // Performing an action after a reset will hide the undo button.
--- a/browser/components/customizableui/test/browser_lwt_telemetry.js
+++ b/browser/components/customizableui/test/browser_lwt_telemetry.js
@@ -11,43 +11,16 @@ add_task(async function testCustomize() 
   Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
   Services.prefs.clearUserPref("lightweightThemes.usedThemes");
   await SpecialPowers.pushPrefEnv({set: [
     ["lightweightThemes.getMoreURL", getMoreURL],
   ]});
 
   await startCustomizing();
 
-  // Open the panel to populate the recommended themes.
-  let themePanel = document.getElementById("customization-lwtheme-menu");
-  themePanel.openPopup();
-  await BrowserTestUtils.waitForPopupEvent(themePanel, "shown");
-
-  // Install a recommended theme.
-  let recommendedLabel = document.getElementById("customization-lwtheme-menu-recommended");
-  let themeButton = recommendedLabel.nextElementSibling;
-  let themeId = `${themeButton.theme.id}@personas.mozilla.org`;
-  let themeChanged = TestUtils.topicObserved("lightweight-theme-changed");
-  themeButton.click();
-
-  // Wait for the theme to change and the popup to close.
-  await themeChanged;
-  await BrowserTestUtils.waitForPopupEvent(themePanel, "hidden");
-
-  // Switch back to the default theme.
-  let installedThemes = document.querySelectorAll(".customization-lwtheme-menu-theme");
-  let defaultId = "default-theme@mozilla.org";
-  let defaultThemeIndex = Array.from(installedThemes).findIndex(btn => btn.theme.id == defaultId);
-  let defaultThemeButton = installedThemes[defaultThemeIndex];
-  themeChanged = TestUtils.topicObserved("lightweight-theme-changed");
-  defaultThemeButton.click();
-
-  // Wait for the theme to change back to default.
-  await themeChanged;
-
   // Find the footer buttons to test.
   let footerRow = document.getElementById("customization-lwtheme-menu-footer");
   let [manageButton, getMoreButton] = footerRow.childNodes;
 
   // Check the manage button, it should open about:addons.
   let waitForNewTab = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
   manageButton.click();
   let addonsTab = await waitForNewTab;
@@ -72,18 +45,16 @@ add_task(async function testCustomize() 
   // Only look at the related events after stripping the timestamp and category.
   let relatedEvents = snapshot.parent
     .filter(([timestamp, category, method, object]) =>
       category == "addonsManager" && object == "customize")
     .map(relatedEvent => relatedEvent.slice(2, 6));
 
   // Events are now [method, object, value, extra] as expected.
   Assert.deepEqual(relatedEvents, [
-    ["action", "customize", "recommended", {action: "enable", addonId: themeId, type: "theme"}],
-    ["action", "customize", null, {action: "enable", addonId: defaultId, type: "theme"}],
     ["link", "customize", "manageThemes"],
     ["link", "customize", "getThemes"],
   ], "The events are recorded correctly");
 
   // Reset the theme prefs to leave them in a clean state.
   Services.prefs.clearUserPref("lightweightThemes.recommendedThemes");
   Services.prefs.clearUserPref("lightweightThemes.usedThemes");
 
--- a/browser/components/uitour/UITour-lib.js
+++ b/browser/components/uitour/UITour-lib.js
@@ -25,24 +25,16 @@ if (typeof Mozilla == "undefined") {
      * browser/app/permissions}.</p>
      *
      * @since 29
      * @namespace
      */
     Mozilla.UITour = {};
   }
 
-  var themeIntervalId = null;
-  function _stopCyclingThemes() {
-    if (themeIntervalId) {
-      clearInterval(themeIntervalId);
-      themeIntervalId = null;
-    }
-  }
-
   function _sendEvent(action, data) {
     var event = new CustomEvent("mozUITour", {
       bubbles: true,
       detail: {
         action,
         data: data || {},
       },
     });
@@ -302,98 +294,16 @@ if (typeof Mozilla == "undefined") {
    * Hide any visible info panels.
    * @see Mozilla.UITour.showInfo
    */
   Mozilla.UITour.hideInfo = function() {
     _sendEvent("hideInfo");
   };
 
   /**
-   * Preview a lightweight-theme applied to the browser UI.
-   *
-   * @see Mozilla.UITour.cycleThemes
-   * @see Mozilla.UITour.resetTheme
-   *
-   * @param {Object} theme - Theme object format expected by `LightweightThemeManager.previewTheme`
-   * @example
-   * var theme = {
-   *   …
-   *   "iconURL":      "https://addons.mozilla.org/_files/…/preview_small.jpg",
-   *   "headerURL":    "https://addons.mozilla.org/_files/….jpg",
-   *   "name":         "My cool theme",
-   *   "author":       "Mozilla",
-   *   "footer":       "https://addons.mozilla.org/_files/….jpg",
-   *   "previewURL":   "https://addons.mozilla.org/_files/…/preview.jpg",
-   *   "updateURL":    "https://versioncheck.addons.mozilla.org/…",
-   *   "accentcolor":  "#000000",
-   *   "header":       "https://addons.mozilla.org/_files/….jpg",
-   *   "version":      "1.0",
-   *   "detailURL":    "https://addons.mozilla.org/firefox/addon/…",
-   *   "textcolor":    "#ffffff",
-   *   "id":           "18066",
-   *   "description":  "My awesome theme.",
-   *   …
-   * };
-   *
-   * Mozilla.UITour.previewTheme(theme);
-   */
-  Mozilla.UITour.previewTheme = function(theme) {
-    _stopCyclingThemes();
-
-    _sendEvent("previewTheme", {
-      theme: JSON.stringify(theme),
-    });
-  };
-
-  /**
-   * Stop previewing and cycling themes, returning to the user's previous theme.
-   * @see Mozilla.UITour.cycleThemes
-   * @see Mozilla.UITour.previewTheme
-   */
-  Mozilla.UITour.resetTheme = function() {
-    _stopCyclingThemes();
-
-    _sendEvent("resetTheme");
-  };
-
-  /**
-   * Cycle between an array of themes using the given delay.
-   *
-   * @see Mozilla.UITour.previewTheme
-   * @see Mozilla.UITour.resetTheme
-   *
-   * @param {Object[]} themes - Array of themes
-   * @param {Number} [delay=Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY]
-   *                 - Time in milliseconds between rotating themes
-   * @param {Function} callback - Function called at each rotation
-   */
-  Mozilla.UITour.cycleThemes = function(themes, delay, callback) {
-    _stopCyclingThemes();
-
-    if (!delay) {
-      delay = Mozilla.UITour.DEFAULT_THEME_CYCLE_DELAY;
-    }
-
-    function nextTheme() {
-      var theme = themes.shift();
-      themes.push(theme);
-
-      _sendEvent("previewTheme", {
-        theme: JSON.stringify(theme),
-        state: true,
-      });
-
-      callback(theme);
-    }
-
-    themeIntervalId = setInterval(nextTheme, delay);
-    nextTheme();
-  };
-
-  /**
    * @typedef {String} Mozilla.UITour.MenuName
    * Valid values:<ul>
    * <li>appMenu
    * <li>bookmarks
    * <li>controlCenter
    * <li>pocket
    * </ul>
    *
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -12,18 +12,16 @@ const {TelemetryController} = ChromeUtil
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 ChromeUtils.defineModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 ChromeUtils.defineModuleGetter(this, "FxAccounts",
   "resource://gre/modules/FxAccounts.jsm");
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
-  "resource://gre/modules/LightweightThemeManager.jsm");
 ChromeUtils.defineModuleGetter(this, "PageActions",
   "resource:///modules/PageActions.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ProfileAge",
   "resource://gre/modules/ProfileAge.jsm");
 ChromeUtils.defineModuleGetter(this, "ReaderParent",
   "resource:///modules/ReaderParent.jsm");
@@ -390,26 +388,16 @@ var UITour = {
         break;
       }
 
       case "hideInfo": {
         this.hideInfo(window);
         break;
       }
 
-      case "previewTheme": {
-        this.previewTheme(data.theme);
-        break;
-      }
-
-      case "resetTheme": {
-        this.resetTheme();
-        break;
-      }
-
       case "showMenu": {
         this.noautohideMenus.add(data.name);
         this.showMenu(window, data.name, () => {
           if (typeof data.showCallbackID == "string")
             this.sendPageCallback(messageManager, data.showCallbackID);
         });
         break;
       }
@@ -775,17 +763,16 @@ var UITour = {
         });
       }
       for (let [ name, listener ] of panel.events) {
         panel.node.removeEventListener(name, listener);
       }
     }
 
     this.noautohideMenus.clear();
-    this.resetTheme();
 
     // If there are no more tour tabs left in the window, teardown the tour for the whole window.
     if (!openTourBrowsers || openTourBrowsers.size == 0) {
       this.teardownTourForWindow(aWindow);
     }
   },
 
   /**
@@ -977,27 +964,16 @@ var UITour = {
     let promise = Promise.all(menuClosePromises);
     await promise;
     if (menuToOpen) {
       promise = this._setMenuStateForAnnotation(aChromeWindow, true, menuToOpen);
     }
     return promise;
   },
 
-  previewTheme(aTheme) {
-    let origin = Services.prefs.getCharPref("browser.uitour.themeOrigin");
-    let data = LightweightThemeManager.parseTheme(aTheme, origin);
-    if (data)
-      LightweightThemeManager.previewTheme(data);
-  },
-
-  resetTheme() {
-    LightweightThemeManager.resetPreview();
-  },
-
   /**
    * The node to which a highlight or notification(-popup) is anchored is sometimes
    * obscured because it may be inside an overflow menu. This function should figure
    * that out and offer the overflow chevron as an alternative.
    *
    * @param {ChromeWindow} aChromeWindow The chrome window
    * @param {Object} aTarget The target object whose node is supposed to be the anchor
    * @type {Node}
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -968,17 +968,16 @@ you can use these alternative items. Oth
 <!ENTITY customizeMode.menuAndToolbars.header3 "Drag your favorite items into the toolbar or overflow menu.">
 <!ENTITY customizeMode.restoreDefaults "Restore Defaults">
 <!ENTITY customizeMode.done "Done">
 <!ENTITY customizeMode.titlebar "Title Bar">
 <!ENTITY customizeMode.extraDragSpace "Drag Space">
 <!ENTITY customizeMode.toolbars2 "Toolbars">
 <!ENTITY customizeMode.lwthemes "Themes">
 <!ENTITY customizeMode.lwthemes.myThemes "My Themes">
-<!ENTITY customizeMode.lwthemes.recommended "Recommended">
 <!ENTITY customizeMode.lwthemes.menuManage "Manage">
 <!ENTITY customizeMode.lwthemes.menuManage.accessKey "M">
 <!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes">
 <!ENTITY customizeMode.lwthemes.menuGetMore.accessKey "G">
 <!ENTITY customizeMode.overflowList.title2 "Overflow Menu">
 <!ENTITY customizeMode.overflowList.description "Drag and drop items here to keep them within reach but out of your toolbar…">
 <!ENTITY customizeMode.uidensity "Density">
 <!-- LOCALIZATION NOTE (customizeMode.uidensity.menuNormal.*):
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/lightweightThemes.properties
+++ /dev/null
@@ -1,12 +0,0 @@
-# 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/.
-
-lightweightThemes.recommended-1.name=A Web Browser Renaissance
-lightweightThemes.recommended-1.description=A Web Browser Renaissance is (C) Sean.Martell. Available under CC-BY-SA. No warranty.
-
-lightweightThemes.recommended-2.name=Space Fantasy
-lightweightThemes.recommended-2.description=Space Fantasy is (C) fx5800p. Available under CC-BY-SA. No warranty.
-
-lightweightThemes.recommended-4.name=Pastel Gradient
-lightweightThemes.recommended-4.description=Pastel Gradient is (C) darrinhenein. Available under CC-BY. No warranty.
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -15,17 +15,16 @@
 # bookmarks.html is produced by LOCALIZED_GENERATED_FILES.
     locale/browser/bookmarks.html                  (bookmarks.html)
 
     locale/browser/accounts.properties             (%chrome/browser/accounts.properties)
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
-    locale/browser/lightweightThemes.properties    (%chrome/browser/lightweightThemes.properties)
     locale/browser/uiDensity.properties            (%chrome/browser/uiDensity.properties)
     locale/browser/pocket.properties               (%chrome/browser/pocket.properties)
     locale/browser/search.properties               (%chrome/browser/search.properties)
     locale/browser/siteData.properties             (%chrome/browser/siteData.properties)
     locale/browser/sitePermissions.properties      (%chrome/browser/sitePermissions.properties)
     locale/browser/setDesktopBackground.dtd        (%chrome/browser/setDesktopBackground.dtd)
     locale/browser/shellservice.properties         (%chrome/browser/shellservice.properties)
     locale/browser/tabbrowser.properties           (%chrome/browser/tabbrowser.properties)
deleted file mode 100644
--- a/dom/asmjscache/AsmJSCache.cpp
+++ /dev/null
@@ -1,1775 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "AsmJSCache.h"
-
-#include <stdio.h>
-
-#include "js/BuildId.h"  // JS::BuildIdCharVector
-#include "js/RootingAPI.h"
-#include "jsfriendapi.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/CondVar.h"
-#include "mozilla/CycleCollectedJSRuntime.h"
-#include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
-#include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
-#include "mozilla/dom/PermissionMessageUtils.h"
-#include "mozilla/dom/quota/Client.h"
-#include "mozilla/dom/quota/QuotaCommon.h"
-#include "mozilla/dom/quota/QuotaManager.h"
-#include "mozilla/dom/quota/QuotaObject.h"
-#include "mozilla/dom/quota/UsageInfo.h"
-#include "mozilla/HashFunctions.h"
-#include "mozilla/ipc/BackgroundChild.h"
-#include "mozilla/ipc/BackgroundParent.h"
-#include "mozilla/ipc/BackgroundUtils.h"
-#include "mozilla/ipc/PBackgroundChild.h"
-#include "mozilla/Telemetry.h"
-#include "mozilla/Unused.h"
-#include "nsAutoPtr.h"
-#include "nsAtom.h"
-#include "nsIFile.h"
-#include "nsIPrincipal.h"
-#include "nsIRunnable.h"
-#include "nsISimpleEnumerator.h"
-#include "nsIThread.h"
-#include "nsJSPrincipals.h"
-#include "nsThreadUtils.h"
-#include "nsXULAppAPI.h"
-#include "prio.h"
-#include "private/pprio.h"
-
-#define ASMJSCACHE_METADATA_FILE_NAME "metadata"
-#define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
-
-using mozilla::HashString;
-using mozilla::Unused;
-using mozilla::dom::quota::AssertIsOnIOThread;
-using mozilla::dom::quota::DirectoryLock;
-using mozilla::dom::quota::PersistenceType;
-using mozilla::dom::quota::QuotaManager;
-using mozilla::dom::quota::QuotaObject;
-using mozilla::dom::quota::UsageInfo;
-using mozilla::ipc::AssertIsOnBackgroundThread;
-using mozilla::ipc::BackgroundChild;
-using mozilla::ipc::IsOnBackgroundThread;
-using mozilla::ipc::PBackgroundChild;
-using mozilla::ipc::PrincipalInfo;
-
-namespace mozilla {
-
-MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
-                                          PR_Close);
-
-namespace dom {
-namespace asmjscache {
-
-namespace {
-
-class ParentRunnable;
-
-// Anything smaller should compile fast enough that caching will just add
-// overhead.
-static const size_t sMinCachedModuleLength = 10000;
-
-// The number of characters to hash into the Metadata::Entry::mFastHash.
-static const unsigned sNumFastHashChars = 4096;
-
-// Track all live parent actors.
-typedef nsTArray<const ParentRunnable*> ParentActorArray;
-StaticAutoPtr<ParentActorArray> sLiveParentActors;
-
-nsresult WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata) {
-  int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE;
-
-  JS::BuildIdCharVector buildId;
-  bool ok = GetBuildId(&buildId);
-  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
-
-  ScopedPRFileDesc fd;
-  nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  uint32_t length = buildId.length();
-  int32_t bytesWritten = PR_Write(fd, &length, sizeof(length));
-  NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED);
-
-  bytesWritten = PR_Write(fd, buildId.begin(), length);
-  NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED);
-
-  bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata));
-  NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
-
-  return NS_OK;
-}
-
-nsresult GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex,
-                      nsIFile** aCacheFile) {
-  nsCOMPtr<nsIFile> cacheFile;
-  nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE);
-  cacheFileName.AppendInt(aModuleIndex);
-  rv = cacheFile->Append(cacheFileName);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  cacheFile.forget(aCacheFile);
-  return NS_OK;
-}
-
-class AutoDecreaseUsageForOrigin {
-  const nsACString& mGroup;
-  const nsACString& mOrigin;
-
- public:
-  uint64_t mFreed;
-
-  AutoDecreaseUsageForOrigin(const nsACString& aGroup,
-                             const nsACString& aOrigin)
-
-      : mGroup(aGroup), mOrigin(aOrigin), mFreed(0) {}
-
-  ~AutoDecreaseUsageForOrigin() {
-    AssertIsOnIOThread();
-
-    if (!mFreed) {
-      return;
-    }
-
-    QuotaManager* qm = QuotaManager::Get();
-    MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
-
-    qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
-                               mOrigin, mFreed);
-  }
-};
-
-static void EvictEntries(nsIFile* aDirectory, const nsACString& aGroup,
-                         const nsACString& aOrigin, uint64_t aNumBytes,
-                         Metadata& aMetadata) {
-  AssertIsOnIOThread();
-
-  AutoDecreaseUsageForOrigin usage(aGroup, aOrigin);
-
-  for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) {
-    Metadata::Entry& entry = aMetadata.mEntries[i];
-    unsigned moduleIndex = entry.mModuleIndex;
-
-    nsCOMPtr<nsIFile> file;
-    nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-
-    bool exists;
-    rv = file->Exists(&exists);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-
-    if (exists) {
-      int64_t fileSize;
-      rv = file->GetFileSize(&fileSize);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return;
-      }
-
-      rv = file->Remove(false);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return;
-      }
-
-      usage.mFreed += fileSize;
-    }
-
-    entry.clear();
-  }
-}
-
-/*******************************************************************************
- * Client
- ******************************************************************************/
-
-class Client : public quota::Client {
-  static Client* sInstance;
-
-  bool mShutdownRequested;
-
- public:
-  Client();
-
-  static bool IsShuttingDownOnBackgroundThread() {
-    AssertIsOnBackgroundThread();
-
-    if (sInstance) {
-      return sInstance->IsShuttingDown();
-    }
-
-    return QuotaManager::IsShuttingDown();
-  }
-
-  static bool IsShuttingDownOnNonBackgroundThread() {
-    MOZ_ASSERT(!IsOnBackgroundThread());
-
-    return QuotaManager::IsShuttingDown();
-  }
-
-  bool IsShuttingDown() const {
-    AssertIsOnBackgroundThread();
-
-    return mShutdownRequested;
-  }
-
-  NS_INLINE_DECL_REFCOUNTING(Client, override)
-
-  Type GetType() override;
-
-  nsresult InitOrigin(PersistenceType aPersistenceType,
-                      const nsACString& aGroup, const nsACString& aOrigin,
-                      const AtomicBool& aCanceled,
-                      UsageInfo* aUsageInfo) override;
-
-  nsresult GetUsageForOrigin(PersistenceType aPersistenceType,
-                             const nsACString& aGroup,
-                             const nsACString& aOrigin,
-                             const AtomicBool& aCanceled,
-                             UsageInfo* aUsageInfo) override;
-
-  void OnOriginClearCompleted(PersistenceType aPersistenceType,
-                              const nsACString& aOrigin) override;
-
-  void ReleaseIOThreadObjects() override;
-
-  void AbortOperations(const nsACString& aOrigin) override;
-
-  void AbortOperationsForProcess(ContentParentId aContentParentId) override;
-
-  void StartIdleMaintenance() override;
-
-  void StopIdleMaintenance() override;
-
-  void ShutdownWorkThreads() override;
-
- private:
-  ~Client() override;
-
-  nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
-                                     const nsACString& aGroup,
-                                     const nsACString& aOrigin,
-                                     const AtomicBool& aCanceled,
-                                     UsageInfo* aUsageInfo,
-                                     const bool aInitializing);
-};
-
-// FileDescriptorHolder owns a file descriptor and its memory mapping.
-// FileDescriptorHolder is derived by two runnable classes (that is,
-// (Parent|Child)Runnable.
-class FileDescriptorHolder : public Runnable {
- public:
-  FileDescriptorHolder()
-      : Runnable("dom::asmjscache::FileDescriptorHolder"),
-        mQuotaObject(nullptr),
-        mFileSize(INT64_MIN),
-        mFileDesc(nullptr),
-        mFileMap(nullptr),
-        mMappedMemory(nullptr) {}
-
-  ~FileDescriptorHolder() override {
-    // These resources should have already been released by Finish().
-    MOZ_ASSERT(!mQuotaObject);
-    MOZ_ASSERT(!mMappedMemory);
-    MOZ_ASSERT(!mFileMap);
-    MOZ_ASSERT(!mFileDesc);
-  }
-
-  size_t FileSize() const {
-    MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file");
-    return mFileSize;
-  }
-
-  PRFileDesc* FileDesc() const {
-    MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file");
-    return mFileDesc;
-  }
-
-  bool MapMemory(OpenMode aOpenMode) {
-    MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice");
-
-    PRFileMapProtect mapFlags =
-        aOpenMode == eOpenForRead ? PR_PROT_READONLY : PR_PROT_READWRITE;
-
-    mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags);
-    NS_ENSURE_TRUE(mFileMap, false);
-
-    mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize);
-    NS_ENSURE_TRUE(mMappedMemory, false);
-
-    return true;
-  }
-
-  void* MappedMemory() const {
-    MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
-    return mMappedMemory;
-  }
-
- protected:
-  // This method must be called before the directory lock is released (the lock
-  // is protecting these resources). It is idempotent, so it is ok to call
-  // multiple times (or before the file has been fully opened).
-  void Finish() {
-    if (mMappedMemory) {
-      PR_MemUnmap(mMappedMemory, mFileSize);
-      mMappedMemory = nullptr;
-    }
-    if (mFileMap) {
-      PR_CloseFileMap(mFileMap);
-      mFileMap = nullptr;
-    }
-    if (mFileDesc) {
-      PR_Close(mFileDesc);
-      mFileDesc = nullptr;
-    }
-
-    // Holding the QuotaObject alive until all the cache files are closed
-    // enables assertions in QuotaManager that the cache entry isn't cleared
-    // while we are working on it.
-    mQuotaObject = nullptr;
-  }
-
-  RefPtr<QuotaObject> mQuotaObject;
-  int64_t mFileSize;
-  PRFileDesc* mFileDesc;
-  PRFileMap* mFileMap;
-  void* mMappedMemory;
-};
-
-// A runnable that implements a state machine required to open a cache entry.
-// It executes in the parent for a cache access originating in the child.
-// This runnable gets registered as an IPDL subprotocol actor so that it
-// can communicate with the corresponding ChildRunnable.
-class ParentRunnable final : public FileDescriptorHolder,
-                             public quota::OpenDirectoryListener,
-                             public PAsmJSCacheEntryParent {
- public:
-  // We need to always declare refcounting because
-  // OpenDirectoryListener has pure-virtual refcounting.
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIRUNNABLE
-
-  ParentRunnable(const PrincipalInfo& aPrincipalInfo, OpenMode aOpenMode,
-                 const WriteParams& aWriteParams)
-      : mOwningEventTarget(GetCurrentThreadEventTarget()),
-        mPrincipalInfo(aPrincipalInfo),
-        mOpenMode(aOpenMode),
-        mWriteParams(aWriteParams),
-        mOperationMayProceed(true),
-        mModuleIndex(0),
-        mState(eInitial),
-        mResult(JS::AsmJSCache_InternalError),
-        mActorDestroyed(false),
-        mOpened(false) {
-    MOZ_ASSERT(XRE_IsParentProcess());
-    AssertIsOnOwningThread();
-  }
-
- private:
-  ~ParentRunnable() override {
-    MOZ_ASSERT(mState == eFinished);
-    MOZ_ASSERT(!mDirectoryLock);
-    MOZ_ASSERT(mActorDestroyed);
-  }
-
-#ifdef DEBUG
-  bool IsOnOwningThread() const {
-    MOZ_ASSERT(mOwningEventTarget);
-
-    bool current;
-    return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
-           current;
-  }
-#endif
-
-  void AssertIsOnOwningThread() const {
-    MOZ_ASSERT(IsOnBackgroundThread());
-    MOZ_ASSERT(IsOnOwningThread());
-  }
-
-  void AssertIsOnNonOwningThread() const {
-    MOZ_ASSERT(!IsOnBackgroundThread());
-    MOZ_ASSERT(!IsOnOwningThread());
-  }
-
-  bool IsActorDestroyed() const {
-    AssertIsOnOwningThread();
-
-    return mActorDestroyed;
-  }
-
-  // May be called on any thread, but you should call IsActorDestroyed() if
-  // you know you're on the background thread because it is slightly faster.
-  bool OperationMayProceed() const { return mOperationMayProceed; }
-
-  // This method is called on the owning thread when the JS engine is finished
-  // reading/writing the cache entry.
-  void Close() {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(mState == eOpened);
-    MOZ_ASSERT(mResult == JS::AsmJSCache_Success);
-
-    mState = eFinished;
-
-    MOZ_ASSERT(mOpened);
-    mOpened = false;
-
-    FinishOnOwningThread();
-
-    if (!mActorDestroyed) {
-      Unused << Send__delete__(this, mResult);
-    }
-  }
-
-  // This method is called upon any failure that prevents the eventual opening
-  // of the cache entry.
-  void Fail() {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(mState != eFinished);
-    MOZ_ASSERT(mResult != JS::AsmJSCache_Success);
-
-    mState = eFinished;
-
-    MOZ_ASSERT(!mOpened);
-
-    FinishOnOwningThread();
-
-    if (!mActorDestroyed) {
-      Unused << Send__delete__(this, mResult);
-    }
-  }
-
-  // The same as method above but is intended to be called off the owning
-  // thread.
-  void FailOnNonOwningThread() {
-    AssertIsOnNonOwningThread();
-    MOZ_ASSERT(mState != eOpened && mState != eFailing && mState != eFinished);
-
-    mState = eFailing;
-    MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-  }
-
-  nsresult InitOnMainThread();
-
-  void OpenDirectory();
-
-  nsresult ReadMetadata();
-
-  nsresult OpenCacheFileForWrite();
-
-  nsresult OpenCacheFileForRead();
-
-  void FinishOnOwningThread();
-
-  void DispatchToIOThread() {
-    AssertIsOnOwningThread();
-
-    if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
-        IsActorDestroyed()) {
-      Fail();
-      return;
-    }
-
-    QuotaManager* qm = QuotaManager::Get();
-    MOZ_ASSERT(qm);
-
-    nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-    if (NS_FAILED(rv)) {
-      Fail();
-      return;
-    }
-  }
-
-  // OpenDirectoryListener overrides.
-  void DirectoryLockAcquired(DirectoryLock* aLock) override;
-
-  void DirectoryLockFailed() override;
-
-  // IPDL methods.
-  void ActorDestroy(ActorDestroyReason why) override {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(!mActorDestroyed);
-    MOZ_ASSERT(mOperationMayProceed);
-
-    mActorDestroyed = true;
-    mOperationMayProceed = false;
-
-    // Assume ActorDestroy can happen at any time, so we can't probe the
-    // current state since mState can be modified on any thread (only one
-    // thread at a time based on the state machine).
-    // However we can use mOpened which is only touched on the owning thread.
-    // If mOpened is true, we can also modify mState since we are guaranteed
-    // that there are no pending runnables which would probe mState to decide
-    // what code needs to run (there shouldn't be any running runnables on
-    // other threads either).
-
-    if (mOpened) {
-      Close();
-
-      MOZ_ASSERT(mState == eFinished);
-    }
-
-    // We don't have to call Fail() if mOpened is not true since it means that
-    // either nothing has been initialized yet, so nothing to cleanup or there
-    // are pending runnables that will detect that the actor has been destroyed
-    // and call Fail().
-  }
-
-  mozilla::ipc::IPCResult RecvSelectCacheFileToRead(
-      const OpenMetadataForReadResponse& aResponse) override {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
-    MOZ_ASSERT(mOpenMode == eOpenForRead);
-    MOZ_ASSERT(!mOpened);
-
-    if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) {
-      Fail();
-      return IPC_OK();
-    }
-
-    switch (aResponse.type()) {
-      case OpenMetadataForReadResponse::TAsmJSCacheResult: {
-        MOZ_ASSERT(aResponse.get_AsmJSCacheResult() != JS::AsmJSCache_Success);
-
-        mResult = aResponse.get_AsmJSCacheResult();
-
-        // This ParentRunnable can only be held alive by the IPDL. Fail()
-        // clears that last reference. So we need to add a self reference here.
-        RefPtr<ParentRunnable> kungFuDeathGrip = this;
-
-        Fail();
-
-        break;
-      }
-
-      case OpenMetadataForReadResponse::Tuint32_t:
-        // A cache entry has been selected to open.
-        mModuleIndex = aResponse.get_uint32_t();
-
-        mState = eReadyToOpenCacheFileForRead;
-
-        DispatchToIOThread();
-
-        break;
-
-      default:
-        MOZ_CRASH("Should never get here!");
-    }
-
-    return IPC_OK();
-  }
-
-  mozilla::ipc::IPCResult RecvClose() override {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(mState == eOpened);
-
-    // This ParentRunnable can only be held alive by the IPDL. Close() clears
-    // that last reference. So we need to add a self reference here.
-    RefPtr<ParentRunnable> kungFuDeathGrip = this;
-
-    Close();
-
-    MOZ_ASSERT(mState == eFinished);
-
-    return IPC_OK();
-  }
-
-  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
-  const PrincipalInfo mPrincipalInfo;
-  const OpenMode mOpenMode;
-  const WriteParams mWriteParams;
-
-  // State initialized during eInitial:
-  nsCString mSuffix;
-  nsCString mGroup;
-  nsCString mOrigin;
-  RefPtr<DirectoryLock> mDirectoryLock;
-
-  // State initialized during eReadyToReadMetadata
-  nsCOMPtr<nsIFile> mDirectory;
-  nsCOMPtr<nsIFile> mMetadataFile;
-  Metadata mMetadata;
-
-  Atomic<bool> mOperationMayProceed;
-
-  // State initialized during eWaitingToOpenCacheFileForRead
-  unsigned mModuleIndex;
-
-  enum State {
-    eInitial,  // Just created, waiting to be dispatched to main thread
-    eWaitingToFinishInit,     // Waiting to finish initialization
-    eWaitingToOpenDirectory,  // Waiting to open directory
-    eWaitingToOpenMetadata,   // Waiting to be called back from OpenDirectory
-    eReadyToReadMetadata,  // Waiting to read the metadata file on the IO thread
-    eSendingMetadataForRead,         // Waiting to send OnOpenMetadataForRead
-    eWaitingToOpenCacheFileForRead,  // Waiting to hear back from child
-    eReadyToOpenCacheFileForRead,    // Waiting to open cache file for read
-    eSendingCacheFile,  // Waiting to send OnOpenCacheFile on the owning thread
-    eOpened,    // Finished calling OnOpenCacheFile, waiting to be closed
-    eFailing,   // Just failed, waiting to be dispatched to the owning thread
-    eFinished,  // Terminal state
-  };
-  State mState;
-  JS::AsmJSCacheResult mResult;
-
-  bool mActorDestroyed;
-  bool mOpened;
-};
-
-nsresult ParentRunnable::InitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == eInitial);
-  MOZ_ASSERT(mPrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
-
-  nsresult rv;
-  nsCOMPtr<nsIPrincipal> principal =
-      PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup,
-                                          &mOrigin);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-void ParentRunnable::OpenDirectory() {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == eWaitingToFinishInit ||
-             mState == eWaitingToOpenDirectory);
-  MOZ_ASSERT(QuotaManager::Get());
-
-  mState = eWaitingToOpenMetadata;
-
-  // XXX The exclusive lock shouldn't be needed for read operations.
-  QuotaManager::Get()->OpenDirectory(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
-                                     mOrigin, quota::Client::ASMJS,
-                                     /* aExclusive */ true, this);
-}
-
-nsresult ParentRunnable::ReadMetadata() {
-  AssertIsOnIOThread();
-  MOZ_ASSERT(mState == eReadyToReadMetadata);
-
-  QuotaManager* qm = QuotaManager::Get();
-  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
-
-  nsresult rv = qm->EnsureOriginIsInitialized(
-      quota::PERSISTENCE_TYPE_TEMPORARY, mSuffix, mGroup, mOrigin,
-      /* aCreateIfNotExists */ true, getter_AddRefs(mDirectory));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mResult = JS::AsmJSCache_StorageInitFailure;
-    return rv;
-  }
-
-  // XXX Bug 1520931 will fully remove asmjs. Just stop creating any new storage
-  // here.
-  return NS_ERROR_FAILURE;
-}
-
-nsresult ParentRunnable::OpenCacheFileForWrite() {
-  AssertIsOnIOThread();
-  MOZ_ASSERT(mState == eReadyToReadMetadata);
-  MOZ_ASSERT(mOpenMode == eOpenForWrite);
-
-  mFileSize = mWriteParams.mSize;
-
-  // Kick out the oldest entry in the LRU queue in the metadata.
-  mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex;
-
-  nsCOMPtr<nsIFile> file;
-  nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  QuotaManager* qm = QuotaManager::Get();
-  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
-
-  // Create the QuotaObject before all file IO and keep it alive until caching
-  // completes to get maximum assertion coverage in QuotaManager against
-  // concurrent removal, etc.
-  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
-                                    mOrigin, file);
-  NS_ENSURE_STATE(mQuotaObject);
-
-  if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
-                                     /* aTruncate */ false)) {
-    // If the request fails, it might be because mOrigin is using too much
-    // space (MaybeUpdateSize will not evict our own origin since it is
-    // active). Try to make some space by evicting LRU entries until there is
-    // enough space.
-    EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
-    if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
-                                       /* aTruncate */ false)) {
-      mResult = JS::AsmJSCache_QuotaExceeded;
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
-  rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Move the mModuleIndex's LRU entry to the recent end of the queue.
-  PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry);
-  Metadata::Entry& entry = mMetadata.mEntries[0];
-  entry.mFastHash = mWriteParams.mFastHash;
-  entry.mNumChars = mWriteParams.mNumChars;
-  entry.mFullHash = mWriteParams.mFullHash;
-  entry.mModuleIndex = mModuleIndex;
-
-  rv = WriteMetadataFile(mMetadataFile, mMetadata);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult ParentRunnable::OpenCacheFileForRead() {
-  AssertIsOnIOThread();
-  MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead);
-  MOZ_ASSERT(mOpenMode == eOpenForRead);
-
-  nsCOMPtr<nsIFile> file;
-  nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  QuotaManager* qm = QuotaManager::Get();
-  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
-
-  // Even though it's not strictly necessary, create the QuotaObject before all
-  // file IO and keep it alive until caching completes to get maximum assertion
-  // coverage in QuotaManager against concurrent removal, etc.
-  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
-                                    mOrigin, file);
-  NS_ENSURE_STATE(mQuotaObject);
-
-  rv = file->GetFileSize(&mFileSize);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
-  rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Move the mModuleIndex's LRU entry to the recent end of the queue.
-  unsigned lruIndex = 0;
-  while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) {
-    if (++lruIndex == Metadata::kNumEntries) {
-      return NS_ERROR_UNEXPECTED;
-    }
-  }
-  Metadata::Entry entry = mMetadata.mEntries[lruIndex];
-  PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex);
-  mMetadata.mEntries[0] = entry;
-
-  rv = WriteMetadataFile(mMetadataFile, mMetadata);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-void ParentRunnable::FinishOnOwningThread() {
-  AssertIsOnOwningThread();
-
-  // Per FileDescriptorHolder::Finish()'s comment, call before
-  // releasing the directory lock.
-  FileDescriptorHolder::Finish();
-
-  mDirectoryLock = nullptr;
-
-  MOZ_ASSERT(sLiveParentActors);
-  sLiveParentActors->RemoveElement(this);
-
-  if (sLiveParentActors->IsEmpty()) {
-    sLiveParentActors = nullptr;
-  }
-}
-
-NS_IMETHODIMP
-ParentRunnable::Run() {
-  nsresult rv;
-
-  // All success/failure paths must eventually call Finish() to avoid leaving
-  // the parser hanging.
-  switch (mState) {
-    case eInitial: {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) ||
-          !OperationMayProceed()) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      rv = InitOnMainThread();
-      if (NS_FAILED(rv)) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      mState = eWaitingToFinishInit;
-      MOZ_ALWAYS_SUCCEEDS(
-          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-
-      return NS_OK;
-    }
-
-    case eWaitingToFinishInit: {
-      AssertIsOnOwningThread();
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
-          IsActorDestroyed()) {
-        Fail();
-        return NS_OK;
-      }
-
-      if (QuotaManager::Get()) {
-        OpenDirectory();
-        return NS_OK;
-      }
-
-      mState = eWaitingToOpenDirectory;
-      QuotaManager::GetOrCreate(this);
-
-      return NS_OK;
-    }
-
-    case eWaitingToOpenDirectory: {
-      AssertIsOnOwningThread();
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
-          IsActorDestroyed()) {
-        Fail();
-        return NS_OK;
-      }
-
-      if (NS_WARN_IF(!QuotaManager::Get())) {
-        Fail();
-        return NS_OK;
-      }
-
-      OpenDirectory();
-      return NS_OK;
-    }
-
-    case eReadyToReadMetadata: {
-      AssertIsOnIOThread();
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) ||
-          !OperationMayProceed()) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      rv = ReadMetadata();
-      if (NS_FAILED(rv)) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      if (mOpenMode == eOpenForRead) {
-        mState = eSendingMetadataForRead;
-        MOZ_ALWAYS_SUCCEEDS(
-            mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-
-        return NS_OK;
-      }
-
-      rv = OpenCacheFileForWrite();
-      if (NS_FAILED(rv)) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      mState = eSendingCacheFile;
-      MOZ_ALWAYS_SUCCEEDS(
-          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-      return NS_OK;
-    }
-
-    case eSendingMetadataForRead: {
-      AssertIsOnOwningThread();
-      MOZ_ASSERT(mOpenMode == eOpenForRead);
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
-          IsActorDestroyed()) {
-        Fail();
-        return NS_OK;
-      }
-
-      mState = eWaitingToOpenCacheFileForRead;
-
-      // Metadata is now open.
-      if (!SendOnOpenMetadataForRead(mMetadata)) {
-        Fail();
-        return NS_OK;
-      }
-
-      return NS_OK;
-    }
-
-    case eReadyToOpenCacheFileForRead: {
-      AssertIsOnIOThread();
-      MOZ_ASSERT(mOpenMode == eOpenForRead);
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) ||
-          !OperationMayProceed()) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      rv = OpenCacheFileForRead();
-      if (NS_FAILED(rv)) {
-        FailOnNonOwningThread();
-        return NS_OK;
-      }
-
-      mState = eSendingCacheFile;
-      MOZ_ALWAYS_SUCCEEDS(
-          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-      return NS_OK;
-    }
-
-    case eSendingCacheFile: {
-      AssertIsOnOwningThread();
-
-      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
-          IsActorDestroyed()) {
-        Fail();
-        return NS_OK;
-      }
-
-      mState = eOpened;
-
-      FileDescriptor::PlatformHandleType handle =
-          FileDescriptor::PlatformHandleType(
-              PR_FileDesc2NativeHandle(mFileDesc));
-      if (!SendOnOpenCacheFile(mFileSize, FileDescriptor(handle))) {
-        Fail();
-        return NS_OK;
-      }
-
-      // The entry is now open.
-      MOZ_ASSERT(!mOpened);
-      mOpened = true;
-
-      mResult = JS::AsmJSCache_Success;
-
-      return NS_OK;
-    }
-
-    case eFailing: {
-      AssertIsOnOwningThread();
-
-      Fail();
-
-      return NS_OK;
-    }
-
-    case eWaitingToOpenMetadata:
-    case eWaitingToOpenCacheFileForRead:
-    case eOpened:
-    case eFinished: {
-      MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
-    }
-  }
-
-  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
-  return NS_OK;
-}
-
-void ParentRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == eWaitingToOpenMetadata);
-  MOZ_ASSERT(!mDirectoryLock);
-
-  mDirectoryLock = aLock;
-
-  mState = eReadyToReadMetadata;
-  DispatchToIOThread();
-}
-
-void ParentRunnable::DirectoryLockFailed() {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == eWaitingToOpenMetadata);
-  MOZ_ASSERT(!mDirectoryLock);
-
-  Fail();
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(ParentRunnable, FileDescriptorHolder)
-
-bool FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
-                   unsigned* aModuleIndex) {
-  // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
-  // also stores an mFastHash of its first sNumFastHashChars so this gives us a
-  // fast way to probabilistically determine whether we have a cache hit. We
-  // still do a full hash of all the chars before returning the cache file to
-  // the engine to avoid penalizing the case where there are multiple cached
-  // asm.js modules where the first sNumFastHashChars are the same. The
-  // mFullHash of each cache entry can have a different mNumChars so the fast
-  // hash allows us to avoid performing up to Metadata::kNumEntries separate
-  // full hashes.
-  uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin;
-  MOZ_ASSERT(numChars > sNumFastHashChars);
-  uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars);
-
-  for (auto entry : aMetadata.mEntries) {
-    // Compare the "fast hash" first to see whether it is worthwhile to
-    // hash all the chars.
-    if (entry.mFastHash != fastHash) {
-      continue;
-    }
-
-    // Assuming we have enough characters, hash all the chars it would take
-    // to match this cache entry and compare to the cache entry. If we get a
-    // hit we'll still do a full source match later (in the JS engine), but
-    // the full hash match means this is probably the cache entry we want.
-    if (numChars < entry.mNumChars) {
-      continue;
-    }
-    uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars);
-    if (entry.mFullHash != fullHash) {
-      continue;
-    }
-
-    *aModuleIndex = entry.mModuleIndex;
-    return true;
-  }
-
-  return false;
-}
-
-}  // unnamed namespace
-
-PAsmJSCacheEntryParent* AllocEntryParent(OpenMode aOpenMode,
-                                         WriteParams aWriteParams,
-                                         const PrincipalInfo& aPrincipalInfo) {
-  AssertIsOnBackgroundThread();
-
-  if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) {
-    return nullptr;
-  }
-
-  if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
-    MOZ_ASSERT(false);
-    return nullptr;
-  }
-
-  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
-    MOZ_ASSERT(false);
-    return nullptr;
-  }
-
-  RefPtr<ParentRunnable> runnable =
-      new ParentRunnable(aPrincipalInfo, aOpenMode, aWriteParams);
-
-  if (!sLiveParentActors) {
-    sLiveParentActors = new ParentActorArray();
-  }
-
-  sLiveParentActors->AppendElement(runnable);
-
-  nsresult rv = NS_DispatchToMainThread(runnable);
-  NS_ENSURE_SUCCESS(rv, nullptr);
-
-  // Transfer ownership to IPDL.
-  return runnable.forget().take();
-}
-
-void DeallocEntryParent(PAsmJSCacheEntryParent* aActor) {
-  // Transfer ownership back from IPDL.
-  RefPtr<ParentRunnable> op = dont_AddRef(static_cast<ParentRunnable*>(aActor));
-}
-
-namespace {
-
-// A runnable that presents a single interface to the AsmJSCache ops which need
-// to wait until the file is open.
-class ChildRunnable final : public FileDescriptorHolder,
-                            public PAsmJSCacheEntryChild {
-  typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
-
- public:
-  class AutoClose {
-    ChildRunnable* mChildRunnable;
-
-   public:
-    explicit AutoClose(ChildRunnable* aChildRunnable = nullptr)
-        : mChildRunnable(aChildRunnable) {}
-
-    void Init(ChildRunnable* aChildRunnable) {
-      MOZ_ASSERT(!mChildRunnable);
-      mChildRunnable = aChildRunnable;
-    }
-
-    ChildRunnable* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
-      MOZ_ASSERT(mChildRunnable);
-      return mChildRunnable;
-    }
-
-    void Forget(ChildRunnable** aChildRunnable) {
-      *aChildRunnable = mChildRunnable;
-      mChildRunnable = nullptr;
-    }
-
-    ~AutoClose() {
-      if (mChildRunnable) {
-        mChildRunnable->Close();
-      }
-    }
-  };
-
-  NS_DECL_NSIRUNNABLE
-
-  ChildRunnable(nsIPrincipal* aPrincipal, OpenMode aOpenMode,
-                const WriteParams& aWriteParams, ReadParams aReadParams)
-      : mPrincipal(aPrincipal),
-        mWriteParams(aWriteParams),
-        mReadParams(aReadParams),
-        mMutex("ChildRunnable::mMutex"),
-        mCondVar(mMutex, "ChildRunnable::mCondVar"),
-        mOpenMode(aOpenMode),
-        mState(eInitial),
-        mResult(JS::AsmJSCache_InternalError),
-        mActorDestroyed(false),
-        mWaiting(false),
-        mOpened(false) {
-    MOZ_ASSERT(!NS_IsMainThread());
-  }
-
-  JS::AsmJSCacheResult BlockUntilOpen(AutoClose* aCloser) {
-    MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once");
-    MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once");
-
-    mWaiting = true;
-
-    nsresult rv = NS_DispatchToMainThread(this);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return JS::AsmJSCache_InternalError;
-    }
-
-    {
-      MutexAutoLock lock(mMutex);
-      while (mWaiting) {
-        mCondVar.Wait();
-      }
-    }
-
-    if (!mOpened) {
-      return mResult;
-    }
-
-    // Now that we're open, we're guaranteed a Close() call. However, we are
-    // not guaranteed someone is holding an outstanding reference until the File
-    // is closed, so we do that ourselves and Release() in OnClose().
-    aCloser->Init(this);
-    AddRef();
-    return JS::AsmJSCache_Success;
-  }
-
-  void Cleanup() {
-#ifdef DEBUG
-    NoteActorDestroyed();
-#endif
-  }
-
- private:
-  ~ChildRunnable() override {
-    MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting");
-    MOZ_ASSERT(!mOpened);
-    MOZ_ASSERT(mState == eFinished);
-    MOZ_ASSERT(mActorDestroyed);
-  }
-
-  // IPDL methods.
-  mozilla::ipc::IPCResult RecvOnOpenMetadataForRead(
-      const Metadata& aMetadata) override {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mState == eOpening);
-
-    uint32_t moduleIndex;
-    bool ok;
-    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
-      ok = SendSelectCacheFileToRead(moduleIndex);
-    } else {
-      ok = SendSelectCacheFileToRead(JS::AsmJSCache_InternalError);
-    }
-    if (!ok) {
-      return IPC_FAIL_NO_REASON(this);
-    }
-
-    return IPC_OK();
-  }
-
-  mozilla::ipc::IPCResult RecvOnOpenCacheFile(
-      const int64_t& aFileSize, const FileDescriptor& aFileDesc) override {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mState == eOpening);
-
-    mFileSize = aFileSize;
-
-    auto rawFD = aFileDesc.ClonePlatformHandle();
-    mFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
-    if (!mFileDesc) {
-      return IPC_FAIL_NO_REASON(this);
-    }
-
-    mState = eOpened;
-    Notify(JS::AsmJSCache_Success);
-    return IPC_OK();
-  }
-
-  mozilla::ipc::IPCResult Recv__delete__(
-      const JS::AsmJSCacheResult& aResult) override {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mState == eOpening || mState == eFinishing);
-    MOZ_ASSERT_IF(mState == eOpening, aResult != JS::AsmJSCache_Success);
-    MOZ_ASSERT_IF(mState == eFinishing, aResult == JS::AsmJSCache_Success);
-
-    if (mState == eOpening) {
-      Fail(aResult);
-    } else {
-      // Match the AddRef in BlockUntilOpen(). The IPDL still holds an
-      // outstanding ref which will keep 'this' alive until ActorDestroy()
-      // is executed.
-      Release();
-
-      mState = eFinished;
-    }
-    return IPC_OK();
-  }
-
-  void ActorDestroy(ActorDestroyReason why) override {
-    MOZ_ASSERT(NS_IsMainThread());
-    NoteActorDestroyed();
-  }
-
-  void Close() {
-    MOZ_ASSERT(mState == eOpened);
-
-    mState = eClosing;
-    NS_DispatchToMainThread(this);
-  }
-
-  void Fail(JS::AsmJSCacheResult aResult) {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mState == eInitial || mState == eOpening);
-    MOZ_ASSERT(aResult != JS::AsmJSCache_Success);
-
-    mState = eFinished;
-
-    FileDescriptorHolder::Finish();
-    Notify(aResult);
-  }
-
-  void Notify(JS::AsmJSCacheResult aResult) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    MutexAutoLock lock(mMutex);
-    MOZ_ASSERT(mWaiting);
-
-    mWaiting = false;
-    mOpened = aResult == JS::AsmJSCache_Success;
-    mResult = aResult;
-    mCondVar.Notify();
-  }
-
-  void NoteActorDestroyed() { mActorDestroyed = true; }
-
-  nsIPrincipal* const mPrincipal;
-  nsAutoPtr<PrincipalInfo> mPrincipalInfo;
-  WriteParams mWriteParams;
-  ReadParams mReadParams;
-  Mutex mMutex;
-  CondVar mCondVar;
-
-  // Couple enums and bools together
-  const OpenMode mOpenMode;
-  enum State {
-    eInitial,  // Just created, waiting to be dispatched to the main thread
-    eOpening,  // Waiting for the parent process to respond
-    eOpened,   // Parent process opened the entry and sent it back
-    eClosing,  // Waiting to be dispatched to the main thread to Send__delete__
-    eFinishing,  // Waiting for the parent process to close
-    eFinished    // Terminal state
-  };
-  State mState;
-  JS::AsmJSCacheResult mResult;
-
-  bool mActorDestroyed;
-  bool mWaiting;
-  bool mOpened;
-};
-
-NS_IMETHODIMP
-ChildRunnable::Run() {
-  switch (mState) {
-    case eInitial: {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      if (mPrincipal->GetIsNullPrincipal()) {
-        NS_WARNING("AsmsJSCache not supported on null principal.");
-        Fail(JS::AsmJSCache_InternalError);
-        return NS_OK;
-      }
-
-      nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
-      nsresult rv = PrincipalToPrincipalInfo(mPrincipal, principalInfo);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        Fail(JS::AsmJSCache_InternalError);
-        return NS_OK;
-      }
-
-      if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(*principalInfo))) {
-        Fail(JS::AsmJSCache_InternalError);
-        return NS_OK;
-      }
-
-      mPrincipalInfo = std::move(principalInfo);
-
-      PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
-      if (NS_WARN_IF(!actor)) {
-        Fail(JS::AsmJSCache_InternalError);
-        return NS_OK;
-      }
-
-      if (!actor->SendPAsmJSCacheEntryConstructor(this, mOpenMode, mWriteParams,
-                                                  *mPrincipalInfo)) {
-        // Unblock the parsing thread with a failure.
-
-        Fail(JS::AsmJSCache_InternalError);
-        return NS_OK;
-      }
-
-      // AddRef to keep this runnable alive until IPDL deallocates the
-      // subprotocol (DeallocEntryChild).
-      AddRef();
-
-      mState = eOpening;
-      return NS_OK;
-    }
-
-    case eClosing: {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      // Per FileDescriptorHolder::Finish()'s comment, call before
-      // releasing the directory lock (which happens in the parent upon receipt
-      // of the Close message).
-      FileDescriptorHolder::Finish();
-
-      MOZ_ASSERT(mOpened);
-      mOpened = false;
-
-      if (mActorDestroyed) {
-        // Match the AddRef in BlockUntilOpen(). The main thread event loop
-        // still holds an outstanding ref which will keep 'this' alive until
-        // returning to the event loop.
-        Release();
-
-        mState = eFinished;
-      } else {
-        Unused << SendClose();
-
-        mState = eFinishing;
-      }
-
-      return NS_OK;
-    }
-
-    case eOpening:
-    case eOpened:
-    case eFinishing:
-    case eFinished: {
-      MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
-    }
-  }
-
-  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
-  return NS_OK;
-}
-
-}  // unnamed namespace
-
-void DeallocEntryChild(PAsmJSCacheEntryChild* aActor) {
-  // Match the AddRef before SendPAsmJSCacheEntryConstructor.
-  static_cast<ChildRunnable*>(aActor)->Release();
-}
-
-namespace {
-
-JS::AsmJSCacheResult OpenFile(nsIPrincipal* aPrincipal, OpenMode aOpenMode,
-                              WriteParams aWriteParams, ReadParams aReadParams,
-                              ChildRunnable::AutoClose* aChildRunnable) {
-  MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0);
-  MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr);
-
-  // There are three reasons we don't attempt caching from the main thread:
-  //  1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
-  //     synchronous waiting on the main thread requiring a runnable to be
-  //     dispatched to the main thread.
-  //  2. In the child process: the IPDL PContent messages we need to
-  //     synchronously wait on are dispatched to the main thread.
-  //  3. While a cache lookup *should* be much faster than compilation, IO
-  //     operations can be unpredictably slow and we'd like to avoid the
-  //     occasional janks on the main thread.
-  // We could use a nested event loop to address 1 and 2, but we're potentially
-  // in the middle of running JS (eval()) and nested event loops can be
-  // semantically observable.
-  if (NS_IsMainThread()) {
-    return JS::AsmJSCache_SynchronousScript;
-  }
-
-  // Check to see whether the principal reflects a private browsing session.
-  // Since AsmJSCache requires disk access at the moment, caching should be
-  // disabled in private browsing situations. Failing here will cause later
-  // read/write requests to also fail.
-  uint32_t pbId;
-  if (NS_WARN_IF(NS_FAILED(aPrincipal->GetPrivateBrowsingId(&pbId)))) {
-    return JS::AsmJSCache_InternalError;
-  }
-
-  if (pbId > 0) {
-    return JS::AsmJSCache_Disabled_PrivateBrowsing;
-  }
-
-  // We need to synchronously call into the parent to open the file and
-  // interact with the QuotaManager. The child can then map the file into its
-  // address space to perform I/O.
-  RefPtr<ChildRunnable> childRunnable =
-      new ChildRunnable(aPrincipal, aOpenMode, aWriteParams, aReadParams);
-
-  JS::AsmJSCacheResult openResult =
-      childRunnable->BlockUntilOpen(aChildRunnable);
-  if (openResult != JS::AsmJSCache_Success) {
-    childRunnable->Cleanup();
-    return openResult;
-  }
-
-  if (!childRunnable->MapMemory(aOpenMode)) {
-    return JS::AsmJSCache_InternalError;
-  }
-
-  return JS::AsmJSCache_Success;
-}
-
-}  // namespace
-
-typedef uint32_t AsmJSCookieType;
-static const uint32_t sAsmJSCookie = 0x600d600d;
-
-bool OpenEntryForRead(nsIPrincipal* aPrincipal, const char16_t* aBegin,
-                      const char16_t* aLimit, size_t* aSize,
-                      const uint8_t** aMemory, intptr_t* aHandle) {
-  if (size_t(aLimit - aBegin) < sMinCachedModuleLength) {
-    return false;
-  }
-
-  ReadParams readParams;
-  readParams.mBegin = aBegin;
-  readParams.mLimit = aLimit;
-
-  ChildRunnable::AutoClose childRunnable;
-  WriteParams notAWrite;
-  JS::AsmJSCacheResult openResult =
-      OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &childRunnable);
-  if (openResult != JS::AsmJSCache_Success) {
-    return false;
-  }
-
-  // Although we trust that the stored cache files have not been arbitrarily
-  // corrupted, it is possible that a previous execution aborted in the middle
-  // of writing a cache file (crash, OOM-killer, etc). To protect against
-  // partially-written cache files, we use the following scheme:
-  //  - Allocate an extra word at the beginning of every cache file which
-  //    starts out 0 (OpenFile opens with PR_TRUNCATE).
-  //  - After the asm.js serialization is complete, PR_SyncMemMap to write
-  //    everything to disk and then store a non-zero value (sAsmJSCookie)
-  //    in the first word.
-  //  - When attempting to read a cache file, check whether the first word is
-  //    sAsmJSCookie.
-  if (childRunnable->FileSize() < sizeof(AsmJSCookieType) ||
-      *(AsmJSCookieType*)childRunnable->MappedMemory() != sAsmJSCookie) {
-    return false;
-  }
-
-  *aSize = childRunnable->FileSize() - sizeof(AsmJSCookieType);
-  *aMemory = (uint8_t*)childRunnable->MappedMemory() + sizeof(AsmJSCookieType);
-
-  // The caller guarnatees a call to CloseEntryForRead (on success or
-  // failure) at which point the file will be closed.
-  childRunnable.Forget(reinterpret_cast<ChildRunnable**>(aHandle));
-  return true;
-}
-
-void CloseEntryForRead(size_t aSize, const uint8_t* aMemory, intptr_t aHandle) {
-  ChildRunnable::AutoClose childRunnable(
-      reinterpret_cast<ChildRunnable*>(aHandle));
-
-  MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize());
-  MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) ==
-             childRunnable->MappedMemory());
-}
-
-JS::AsmJSCacheResult OpenEntryForWrite(nsIPrincipal* aPrincipal,
-                                       const char16_t* aBegin,
-                                       const char16_t* aEnd, size_t aSize,
-                                       uint8_t** aMemory, intptr_t* aHandle) {
-  if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
-    return JS::AsmJSCache_ModuleTooSmall;
-  }
-
-  // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
-  aSize += sizeof(AsmJSCookieType);
-
-  static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
-
-  WriteParams writeParams;
-  writeParams.mSize = aSize;
-  writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
-  writeParams.mNumChars = aEnd - aBegin;
-  writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
-
-  ChildRunnable::AutoClose childRunnable;
-  ReadParams notARead;
-  JS::AsmJSCacheResult openResult = OpenFile(
-      aPrincipal, eOpenForWrite, writeParams, notARead, &childRunnable);
-  if (openResult != JS::AsmJSCache_Success) {
-    return openResult;
-  }
-
-  // Strip off the AsmJSCookieType from the buffer returned to the caller,
-  // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
-  // with a cookie.
-  *aMemory = (uint8_t*)childRunnable->MappedMemory() + sizeof(AsmJSCookieType);
-
-  // The caller guarnatees a call to CloseEntryForWrite (on success or
-  // failure) at which point the file will be closed
-  childRunnable.Forget(reinterpret_cast<ChildRunnable**>(aHandle));
-  return JS::AsmJSCache_Success;
-}
-
-void CloseEntryForWrite(size_t aSize, uint8_t* aMemory, intptr_t aHandle) {
-  ChildRunnable::AutoClose childRunnable(
-      reinterpret_cast<ChildRunnable*>(aHandle));
-
-  MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize());
-  MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) ==
-             childRunnable->MappedMemory());
-
-  // Flush to disk before writing the cookie (see OpenEntryForRead).
-  if (PR_SyncMemMap(childRunnable->FileDesc(), childRunnable->MappedMemory(),
-                    childRunnable->FileSize()) == PR_SUCCESS) {
-    *(AsmJSCookieType*)childRunnable->MappedMemory() = sAsmJSCookie;
-  }
-}
-
-/*******************************************************************************
- * Client
- ******************************************************************************/
-
-Client* Client::sInstance = nullptr;
-
-Client::Client() : mShutdownRequested(false) {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
-
-  sInstance = this;
-}
-
-Client::~Client() {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
-
-  sInstance = nullptr;
-}
-
-Client::Type Client::GetType() { return ASMJS; }
-
-nsresult Client::InitOrigin(PersistenceType aPersistenceType,
-                            const nsACString& aGroup, const nsACString& aOrigin,
-                            const AtomicBool& aCanceled,
-                            UsageInfo* aUsageInfo) {
-  if (!aUsageInfo) {
-    return NS_OK;
-  }
-  return GetUsageForOriginInternal(aPersistenceType, aGroup, aOrigin, aCanceled,
-                                   aUsageInfo, /* aInitializing */ true);
-}
-
-nsresult Client::GetUsageForOrigin(PersistenceType aPersistenceType,
-                                   const nsACString& aGroup,
-                                   const nsACString& aOrigin,
-                                   const AtomicBool& aCanceled,
-                                   UsageInfo* aUsageInfo) {
-  return GetUsageForOriginInternal(aPersistenceType, aGroup, aOrigin, aCanceled,
-                                   aUsageInfo, /* aInitializing */ false);
-}
-
-void Client::OnOriginClearCompleted(PersistenceType aPersistenceType,
-                                    const nsACString& aOrigin) {}
-
-void Client::ReleaseIOThreadObjects() {}
-
-void Client::AbortOperations(const nsACString& aOrigin) {}
-
-void Client::AbortOperationsForProcess(ContentParentId aContentParentId) {}
-
-void Client::StartIdleMaintenance() {}
-
-void Client::StopIdleMaintenance() {}
-
-void Client::ShutdownWorkThreads() {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!mShutdownRequested);
-
-  mShutdownRequested = true;
-
-  if (sLiveParentActors) {
-    MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return !sLiveParentActors; }));
-  }
-}
-
-nsresult Client::GetUsageForOriginInternal(PersistenceType aPersistenceType,
-                                           const nsACString& aGroup,
-                                           const nsACString& aOrigin,
-                                           const AtomicBool& aCanceled,
-                                           UsageInfo* aUsageInfo,
-                                           const bool aInitializing) {
-  QuotaManager* qm = QuotaManager::Get();
-  MOZ_ASSERT(qm, "We were being called by the QuotaManager");
-#ifndef NIGHTLY_BUILD
-  Unused << aInitializing;
-#endif
-
-  nsCOMPtr<nsIFile> directory;
-  nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
-                                          getter_AddRefs(directory));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
-                                 Asm_GetDirForOrigin);
-    return rv;
-  }
-
-  MOZ_ASSERT(directory, "We're here because the origin directory exists");
-
-  rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError, Asm_Append);
-    return rv;
-  }
-
-  DebugOnly<bool> exists;
-  MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
-
-  nsCOMPtr<nsIDirectoryEnumerator> entries;
-  rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
-                                 Asm_GetDirEntries);
-    return rv;
-  }
-
-  nsCOMPtr<nsIFile> file;
-  while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
-         file && !aCanceled) {
-    int64_t fileSize;
-    rv = file->GetFileSize(&fileSize);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
-                                   Asm_GetFileSize);
-      return rv;
-    }
-
-    MOZ_ASSERT(fileSize >= 0, "Negative size?!");
-
-    // Since the client is not explicitly storing files, append to database
-    // usage which represents implicit storage allocation.
-    aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
-                                 Asm_GetNextFile);
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-quota::Client* CreateClient() { return new Client(); }
-
-}  // namespace asmjscache
-}  // namespace dom
-}  // namespace mozilla
-
-namespace IPC {
-
-using mozilla::dom::asmjscache::Metadata;
-using mozilla::dom::asmjscache::WriteParams;
-
-void ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam) {
-  for (auto entry : aParam.mEntries) {
-    WriteParam(aMsg, entry.mFastHash);
-    WriteParam(aMsg, entry.mNumChars);
-    WriteParam(aMsg, entry.mFullHash);
-    WriteParam(aMsg, entry.mModuleIndex);
-  }
-}
-
-bool ParamTraits<Metadata>::Read(const Message* aMsg, PickleIterator* aIter,
-                                 paramType* aResult) {
-  for (auto& entry : aResult->mEntries) {
-    if (!ReadParam(aMsg, aIter, &entry.mFastHash) ||
-        !ReadParam(aMsg, aIter, &entry.mNumChars) ||
-        !ReadParam(aMsg, aIter, &entry.mFullHash) ||
-        !ReadParam(aMsg, aIter, &entry.mModuleIndex)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-void ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog) {
-  for (auto entry : aParam.mEntries) {
-    LogParam(entry.mFastHash, aLog);
-    LogParam(entry.mNumChars, aLog);
-    LogParam(entry.mFullHash, aLog);
-    LogParam(entry.mModuleIndex, aLog);
-  }
-}
-
-void ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam) {
-  WriteParam(aMsg, aParam.mSize);
-  WriteParam(aMsg, aParam.mFastHash);
-  WriteParam(aMsg, aParam.mNumChars);
-  WriteParam(aMsg, aParam.mFullHash);
-}
-
-bool ParamTraits<WriteParams>::Read(const Message* aMsg, PickleIterator* aIter,
-                                    paramType* aResult) {
-  return ReadParam(aMsg, aIter, &aResult->mSize) &&
-         ReadParam(aMsg, aIter, &aResult->mFastHash) &&
-         ReadParam(aMsg, aIter, &aResult->mNumChars) &&
-         ReadParam(aMsg, aIter, &aResult->mFullHash);
-}
-
-void ParamTraits<WriteParams>::Log(const paramType& aParam,
-                                   std::wstring* aLog) {
-  LogParam(aParam.mSize, aLog);
-  LogParam(aParam.mFastHash, aLog);
-  LogParam(aParam.mNumChars, aLog);
-  LogParam(aParam.mFullHash, aLog);
-}
-
-}  // namespace IPC
deleted file mode 100644
--- a/dom/asmjscache/AsmJSCache.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_asmjscache_asmjscache_h
-#define mozilla_dom_asmjscache_asmjscache_h
-
-#include "ipc/IPCMessageUtils.h"
-#include "js/TypeDecls.h"
-#include "js/Vector.h"
-#include "jsapi.h"
-
-class nsIPrincipal;
-
-namespace JS {
-
-enum AsmJSCacheResult {
-  AsmJSCache_Success,
-  AsmJSCache_MIN = AsmJSCache_Success,
-  AsmJSCache_ModuleTooSmall,
-  AsmJSCache_SynchronousScript,
-  AsmJSCache_QuotaExceeded,
-  AsmJSCache_StorageInitFailure,
-  AsmJSCache_Disabled_Internal,
-  AsmJSCache_Disabled_ShellFlags,
-  AsmJSCache_Disabled_JitInspector,
-  AsmJSCache_InternalError,
-  AsmJSCache_Disabled_PrivateBrowsing,
-  AsmJSCache_LIMIT
-};
-
-}  // namespace JS
-
-namespace mozilla {
-
-namespace ipc {
-
-class PrincipalInfo;
-
-}  // namespace ipc
-
-namespace dom {
-
-namespace quota {
-class Client;
-}  // namespace quota
-
-namespace asmjscache {
-
-class PAsmJSCacheEntryChild;
-class PAsmJSCacheEntryParent;
-
-enum OpenMode { eOpenForRead, eOpenForWrite, NUM_OPEN_MODES };
-
-// Each origin stores a fixed size (kNumEntries) LRU cache of compiled asm.js
-// modules. Each compiled asm.js module is stored in a separate file with one
-// extra metadata file that stores the LRU cache and enough information for a
-// client to pick which cached module's file to open.
-struct Metadata {
-  static const unsigned kNumEntries = 16;
-  static const unsigned kLastEntry = kNumEntries - 1;
-
-  struct Entry {
-    uint32_t mFastHash;
-    uint32_t mNumChars;
-    uint32_t mFullHash;
-    unsigned mModuleIndex;
-
-    void clear() {
-      mFastHash = -1;
-      mNumChars = -1;
-      mFullHash = -1;
-    }
-  };
-
-  Entry mEntries[kNumEntries];
-};
-
-// Parameters specific to opening a cache entry for writing
-struct WriteParams {
-  int64_t mSize;
-  int64_t mFastHash;
-  int64_t mNumChars;
-  int64_t mFullHash;
-
-  WriteParams() : mSize(0), mFastHash(0), mNumChars(0), mFullHash(0) {}
-};
-
-// Parameters specific to opening a cache entry for reading
-struct ReadParams {
-  const char16_t* mBegin;
-  const char16_t* mLimit;
-
-  ReadParams() : mBegin(nullptr), mLimit(nullptr) {}
-};
-
-// Implementation of AsmJSCacheOps, installed for the main JSRuntime by
-// nsJSEnvironment.cpp and DOM Worker JSRuntimes in RuntimeService.cpp.
-//
-// The Open* functions cannot be called directly from AsmJSCacheOps: they take
-// an nsIPrincipal as the first argument instead of a Handle<JSObject*>. The
-// caller must map the object to an nsIPrincipal.
-//
-// These methods may be called off the main thread and guarantee not to
-// access the given aPrincipal except on the main thread. In exchange, the
-// caller must ensure the given principal is alive from when OpenEntryForX is
-// called to when CloseEntryForX returns.
-
-bool OpenEntryForRead(nsIPrincipal* aPrincipal, const char16_t* aBegin,
-                      const char16_t* aLimit, size_t* aSize,
-                      const uint8_t** aMemory, intptr_t* aHandle);
-void CloseEntryForRead(size_t aSize, const uint8_t* aMemory, intptr_t aHandle);
-JS::AsmJSCacheResult OpenEntryForWrite(nsIPrincipal* aPrincipal,
-                                       const char16_t* aBegin,
-                                       const char16_t* aEnd, size_t aSize,
-                                       uint8_t** aMemory, intptr_t* aHandle);
-void CloseEntryForWrite(size_t aSize, uint8_t* aMemory, intptr_t aHandle);
-
-// Called from QuotaManager.cpp:
-
-quota::Client* CreateClient();
-
-// Called from ipc/ContentParent.cpp:
-
-PAsmJSCacheEntryParent* AllocEntryParent(
-    OpenMode aOpenMode, WriteParams aWriteParams,
-    const mozilla::ipc::PrincipalInfo& aPrincipalInfo);
-
-void DeallocEntryParent(PAsmJSCacheEntryParent* aActor);
-
-// Called from ipc/ContentChild.cpp:
-
-void DeallocEntryChild(PAsmJSCacheEntryChild* aActor);
-
-}  // namespace asmjscache
-}  // namespace dom
-}  // namespace mozilla
-
-namespace IPC {
-
-template <>
-struct ParamTraits<mozilla::dom::asmjscache::OpenMode>
-    : public ContiguousEnumSerializer<
-          mozilla::dom::asmjscache::OpenMode,
-          mozilla::dom::asmjscache::eOpenForRead,
-          mozilla::dom::asmjscache::NUM_OPEN_MODES> {};
-
-template <>
-struct ParamTraits<mozilla::dom::asmjscache::Metadata> {
-  typedef mozilla::dom::asmjscache::Metadata paramType;
-  static void Write(Message* aMsg, const paramType& aParam);
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult);
-  static void Log(const paramType& aParam, std::wstring* aLog);
-};
-
-template <>
-struct ParamTraits<mozilla::dom::asmjscache::WriteParams> {
-  typedef mozilla::dom::asmjscache::WriteParams paramType;
-  static void Write(Message* aMsg, const paramType& aParam);
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult);
-  static void Log(const paramType& aParam, std::wstring* aLog);
-};
-
-template <>
-struct ParamTraits<JS::AsmJSCacheResult>
-    : public ContiguousEnumSerializer<JS::AsmJSCacheResult, JS::AsmJSCache_MIN,
-                                      JS::AsmJSCache_LIMIT> {};
-
-}  // namespace IPC
-
-#endif  // mozilla_dom_asmjscache_asmjscache_h
deleted file mode 100644
--- a/dom/asmjscache/PAsmJSCacheEntry.ipdl
+++ /dev/null
@@ -1,53 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-include protocol PBackground;
-
-using mozilla::dom::asmjscache::Metadata from "mozilla/dom/asmjscache/AsmJSCache.h";
-using JS::AsmJSCacheResult from "mozilla/dom/asmjscache/AsmJSCache.h";
-
-namespace mozilla {
-namespace dom {
-namespace asmjscache {
-
-union OpenMetadataForReadResponse
-{
-  AsmJSCacheResult;
-  uint32_t;
-};
-
-protocol PAsmJSCacheEntry
-{
-  manager PBackground;
-
-  // When the cache is opened to read, the parent process sends over the
-  // origin's Metadata so the child process can select the cache entry to open
-  // (based on hash) and notify the parent (via SelectCacheFileToRead).
-child:
-  async OnOpenMetadataForRead(Metadata metadata);
-parent:
-  async SelectCacheFileToRead(OpenMetadataForReadResponse response);
-
-child:
-  // Once the cache file has been opened, the child is notified and sent an
-  // open file descriptor.
-  async OnOpenCacheFile(int64_t fileSize, FileDescriptor fileDesc);
-
-parent:
-  // When the child process is done with the cache entry, the parent process
-  // is notified (via Close).
-  async Close();
-
-child:
-  // When there's an error during the opening phase, the child process is
-  // notified (via __delete__) and sent an error result.
-  // When the parent process receives the Close message, it closes the cache
-  // entry on the parent side and the child is notified (via __delete__).
-  // The protocol is destroyed in both cases.
-  async __delete__(AsmJSCacheResult result);
-};
-
-} // namespace asmjscache
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/asmjscache/moz.build
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-with Files("**"):
-    BUG_COMPONENT = ("Core", "JavaScript Engine")
-
-EXPORTS.mozilla.dom.asmjscache += [
-    'AsmJSCache.h'
-]
-
-SOURCES += [
-    'AsmJSCache.cpp'
-]
-
-IPDL_SOURCES += [
-    'PAsmJSCacheEntry.ipdl'
-]
-
-include('/ipc/chromium/chromium-config.mozbuild')
-
-FINAL_LIBRARY = 'xul'
-
-MOCHITEST_MANIFESTS += ['test/mochitest.ini']
deleted file mode 100644
--- a/dom/asmjscache/test/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  "extends": [
-    "plugin:mozilla/mochitest-test",
-  ],
-};
deleted file mode 100644
--- a/dom/asmjscache/test/file_slow.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/* globals jsFuns:false, complete:false */
-function f1() { "use asm"; function g() {} return g; }
-if (this.jsFuns) {
-    ok(jsFuns.isAsmJSModule(f1), "f1 is an asm.js module");
-    ok(jsFuns.isAsmJSFunction(f1()), "f1.g is an asm.js function");
-}
-
-function f2(stdlib, foreign, buffer) {
-    "use asm";
-    var i32 = new stdlib.Int32Array(buffer);
-    function main(n) {
-        n = n|0;
-        var i = 0, sum = 0;
-        for (; (i|0) < (n|0); i=(i + 1)|0)
-            sum = (sum + (i32[(i << 2) >> 2]|0))|0;
-        return sum|0;
-    }
-    return main;
-}
-if (this.jsFuns)
-    ok(jsFuns.isAsmJSModule(f2), "f2 is an asm.js module");
-var i32 = new Int32Array(16384); // Smallest allowed buffer size is 64KBy
-for (let i = 0; i < i32.length; i++)
-    i32[i] = i;
-var f2Main = f2(this, null, i32.buffer);
-if (this.jsFuns)
-    ok(jsFuns.isAsmJSFunction(f2Main), "f2.main is an asm.js function");
-if (f2Main(4) !== 6)
-    throw new Error("f2Main(4)");
-if (f2Main(100) !== 4950)
-    throw new Error("f2.main(100)");
-var sum = (((i32.length - 1) * i32.length) / 2);
-if (f2Main(i32.length) !== sum)
-    throw new Error(`f2.main(${i32.length})`);
-if (f2Main(i32.length + 100) !== sum)
-    throw new Error(`f2.main(${i32.length})`);
-
-/* eslint-disable no-unused-vars,no-shadow */
-function f3(stdlib, foreign, buffer) {
-    "use asm";
-    var doneFunc = foreign.done;
-    var i32 = new stdlib.Int32Array(buffer);
-    function main() {
-        var i = 0;
-        var total = 0;
-        while (1) {
-            for (i = 0; (i|0) < 1000; i=(i + 1)|0)
-                total = (total + i)|0;
-            if (doneFunc(total|0)|0)
-                break;
-        }
-        return total|0;
-    }
-    return main;
-}
-/* eslint-enable no-unused-vars,no-shadow */
-
-var begin;
-var lastSum;
-function done(sumInner) {
-    if (sumInner !== ((lastSum + 499500)|0))
-        throw new Error(`bad sum: ${sumInner}, ${lastSum}, ${((lastSum + 499500)|0)}`);
-    lastSum = sumInner;
-    return (Date.now() - begin) > 3000;
-}
-var f3Main = f3(this, {done}, i32.buffer);
-if (this.jsFuns)
-    ok(jsFuns.isAsmJSFunction(f3Main), "f3.main is an asm.js function");
-
-begin = Date.now();
-lastSum = 0;
-if (f3Main() !== lastSum)
-    throw new Error("f3.main()");
-
-if (!this.jsFuns)
-    postMessage("ok");
-else
-    complete();
deleted file mode 100644
--- a/dom/asmjscache/test/mochitest.ini
+++ /dev/null
@@ -1,14 +0,0 @@
-[DEFAULT]
-support-files =
-  file_slow.js
-
-[test_cachingBasic.html]
-disabled = bug 1520931
-[test_workers.html]
-disabled = bug 1520931
-[test_cachingMulti.html]
-disabled = bug 1520931
-[test_slow.html]
-# bug 929498
-skip-if = os == 'android'
-disabled = bug 1520931
deleted file mode 100644
--- a/dom/asmjscache/test/test_cachingBasic.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=929236
--->
-<head>
-  <meta charset="utf-8">
-  <title>asm.js browser tests</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=929236">asm.js browser tests</a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test"></pre>
-
-  <script>
-  var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-
-  var code = "function f() { 'use asm';\n";
-  for (var i = 0; i < 5000; i++)
-    code += "function g" + i + "() { return " + i + "}\n";
-  code += "return g42 }\n";
-  code += "ok(jsFuns.isAsmJSModule(f), 'f is an asm.js module')\n";
-  code += "var g42 = f();\n";
-  code += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function')\n";
-  code += "ok(g42() === 42, 'g42 returns the correct result')\n";
-  code += "finishedEvalAsync(f);";
-  ok(code.length > 100000, "code is long enough to definitely hit the cache");
-
-  function evalAsync(codeInner) {
-    var blob = new Blob([codeInner], {type: "application/javascript"});
-    var script = document.createElement("script");
-    script.src = URL.createObjectURL(blob);
-    document.body.appendChild(script);
-  }
-
-  var state = 0;
-  function finishedEvalAsync(module) {
-    switch (state) {
-      case 0:
-        state++;
-        evalAsync(code);
-        break;
-      case 1:
-        ok(jsFuns.isAsmJSModule(module), "module");
-        SimpleTest.finish();
-        break;
-      default:
-        throw new Error("huh?");
-    }
-  }
-
-  function runTest() {
-      // generate a big ol asm.js module and compile it async so that we can hit
-      // the asm.js cache.
-      SimpleTest.waitForExplicitFinish();
-      evalAsync(code);
-  }
-
-  if (!jsFuns.isAsmJSCompilationAvailable()) {
-      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
-  } else {
-      runTest();
-  }
-  </script>
-
-</body>
-</html>
deleted file mode 100644
--- a/dom/asmjscache/test/test_cachingMulti.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=944821
--->
-<head>
-  <meta charset="utf-8">
-  <title>asm.js browser tests</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=944821">asm.js browser tests</a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test"></pre>
-
-  <script>
-  var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-
-  // generate four slightly different big asm.js modules and compile them async
-  // so that we can hit the asm.js cache.
-
-  var code = "function f() { 'use asm';\n";
-  for (let i = 0; i < 5000; i++)
-    code += "function g" + i + "() { return " + i + "}\n";
-  ok(code.length > 100000, "code is long enough to definitely hit the cache");
-
-  const N = 4;
-
-  var codes = [];
-  for (let i = 0; i < N; i++) {
-    var code2 = code;
-    code2 += "return g" + i + ";\n";
-    code2 += "}\n";
-    code2 += "ok(jsFuns.isAsmJSModule(f), 'f is an asm.js module')\n";
-    code2 += "var gX = f();\n";
-    code2 += "ok(jsFuns.isAsmJSFunction(gX), 'gX is an asm.js function')\n";
-    code2 += "ok(gX() === " + i + ", 'gX returns the correct result')\n";
-    code2 += "finishedEvalAsync();\n";
-    codes.push(code2);
-  }
-
-  function evalAsync(codeInner) {
-    var blob = new Blob([codeInner], {type: "application/javascript"});
-    var script = document.createElement("script");
-    script.src = URL.createObjectURL(blob);
-    document.body.appendChild(script);
-  }
-
-  var finishedCount = 0;
-  function finishedEvalAsync() {
-    finishedCount++;
-
-    if (finishedCount < 1 || finishedCount > 2 * N) {
-      throw new Error("Huh?!");
-    } else if (finishedCount == N) {
-      for (let i = 0; i < N; i++)
-        evalAsync(codes[i]);
-    } else if (finishedCount == 2 * N) {
-      SimpleTest.finish();
-    }
-  }
-
-  function runTest() {
-      for (let i = 0; i < N; i++)
-        evalAsync(codes[i]);
-
-      SimpleTest.waitForExplicitFinish();
-  }
-
-  if (!jsFuns.isAsmJSCompilationAvailable()) {
-      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
-  } else {
-      runTest();
-  }
-  </script>
-
-</body>
-</html>
-
deleted file mode 100644
--- a/dom/asmjscache/test/test_slow.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=854209
--->
-<head>
-  <meta charset="utf-8">
-  <title>asm.js browser tests</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=854209">asm.js browser tests</a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test"></pre>
-
-  <script>
-  var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-
-  var completed = 0;
-  function complete() {
-      if (++completed == 2)
-          SimpleTest.finish();
-  }
-
-  if (!jsFuns.isAsmJSCompilationAvailable()) {
-      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
-  } else {
-      var script = document.createElement("script");
-      script.src = "http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js";
-      document.body.appendChild(script);
-
-      var w = new Worker("http://mochi.test:8888/tests/dom/asmjscache/test/file_slow.js");
-      w.onmessage = function(e) {
-          ok(e.data === "ok", "Worker asm.js tests");
-          complete();
-      };
-
-      SimpleTest.waitForExplicitFinish();
-  }
-  </script>
-
-  <script>
-  </script>
-
-</body>
-</html>
deleted file mode 100644
--- a/dom/asmjscache/test/test_workers.html
+++ /dev/null
@@ -1,74 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=941830
--->
-<head>
-  <meta charset="utf-8">
-  <title>asm.js browser tests</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=941830">asm.js browser tests</a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test"></pre>
-
-  <script>
-  var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();
-
-  function runTest() {
-      var asmjsCode = "function f() { 'use asm';";
-      for (var i = 0; i < 5000; i++)
-        asmjsCode += "function g" + i + "() { return " + i + "}";
-      asmjsCode += "return g42 }";
-      ok(asmjsCode.length > 100000, "code is long enough to definitely hit the cache");
-
-      var workerCode = asmjsCode;
-      workerCode += "if (f()() !== 42) postMessage('fail'); else postMessage('ok');";
-      workerCode = 'var code = "' + workerCode + '"; eval(code); eval(code)';
-      var workerBlob = new Blob([workerCode], {type: "application/javascript"});
-
-      var mainCode = asmjsCode;
-      mainCode += "ok(jsFuns.isAsmJSModule(f), 'f is a module')\n";
-      mainCode += "var g42 = f();\n";
-      mainCode += "ok(jsFuns.isAsmJSFunction(g42), 'g42 is an asm.js function');\n";
-      mainCode += "ok(g42() === 42, 'g42 returns the correct result');\n";
-      mainCode += "SimpleTest.finish();\n";
-      var mainBlob = new Blob([mainCode], {type: "application/javascript"});
-
-      var w = new Worker(URL.createObjectURL(workerBlob));
-
-      var received = 0;
-      w.onmessage = function(e) {
-          switch (received) {
-            case 0:
-              ok(e.data === "ok", "Received first message");
-              received = 1;
-              break;
-            case 1:
-              ok(e.data === "ok", "Received second message");
-              received = 2;
-
-              var script = document.createElement("script");
-              script.src = URL.createObjectURL(mainBlob);
-              document.body.appendChild(script);
-              break;
-            default:
-              throw new Error("Huh?");
-          }
-      };
-
-      SimpleTest.waitForExplicitFinish();
-  }
-
-  if (!jsFuns.isAsmJSCompilationAvailable()) {
-      ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
-  } else {
-      runTest();
-  }
-  </script>
-
-</body>
-</html>
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -256,16 +256,27 @@ WebRenderBridgeChild* nsDOMWindowUtils::
       if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) {
         return wrlm->WrBridge();
       }
     }
   }
   return nullptr;
 }
 
+CompositorBridgeChild* nsDOMWindowUtils::GetCompositorBridge() {
+  if (nsIWidget* widget = GetWidget()) {
+    if (LayerManager* lm = widget->GetLayerManager()) {
+      if (CompositorBridgeChild* cbc = lm->GetCompositorBridgeChild()) {
+        return cbc;
+      }
+    }
+  }
+  return nullptr;
+}
+
 NS_IMETHODIMP
 nsDOMWindowUtils::SyncFlushCompositor() {
   if (nsIWidget* widget = GetWidget()) {
     if (LayerManager* lm = widget->GetLayerManager()) {
       if (KnowsCompositor* kc = lm->AsKnowsCompositor()) {
         kc->SyncWithCompositor();
       }
     }
@@ -4095,16 +4106,28 @@ NS_IMETHODIMP
 nsDOMWindowUtils::WrCapture() {
   if (WebRenderBridgeChild* wrbc = GetWebRenderBridge()) {
     wrbc->Capture();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SetCompositionRecording(bool aValue) {
+  if (CompositorBridgeChild* cbc = GetCompositorBridge()) {
+    if (aValue) {
+      cbc->SendBeginRecording(TimeStamp::Now());
+    } else {
+      cbc->SendEndRecording();
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::SetSystemFont(const nsACString& aFontName) {
   nsIWidget* widget = GetWidget();
   if (!widget) {
     return NS_OK;
   }
 
   nsAutoCString fontName(aFontName);
   return widget->SetSystemFont(fontName);
--- a/dom/base/nsDOMWindowUtils.h
+++ b/dom/base/nsDOMWindowUtils.h
@@ -77,16 +77,17 @@ class nsDOMWindowUtils final : public ns
   nsIWidget* GetWidget(nsPoint* aOffset = nullptr);
   nsIWidget* GetWidgetForElement(mozilla::dom::Element* aElement);
 
   nsIPresShell* GetPresShell();
   nsPresContext* GetPresContext();
   mozilla::dom::Document* GetDocument();
   mozilla::layers::LayerTransactionChild* GetLayerTransaction();
   mozilla::layers::WebRenderBridgeChild* GetWebRenderBridge();
+  mozilla::layers::CompositorBridgeChild* GetCompositorBridge();
 
   // Until callers are annotated.
   MOZ_CAN_RUN_SCRIPT
   NS_IMETHOD SendMouseEventCommon(
       const nsAString& aType, float aX, float aY, int32_t aButton,
       int32_t aClickCount, int32_t aModifiers, bool aIgnoreRootScrollFrame,
       float aPressure, unsigned short aInputSourceArg, uint32_t aIdentifier,
       bool aToWindow, bool* aPreventDefault, bool aIsDOMEventSynthesized,
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -15689,18 +15689,28 @@ QuotaClient::~QuotaClient() {
   gTelemetryIdMutex = nullptr;
 
   sInstance = nullptr;
 }
 
 nsresult QuotaClient::AsyncDeleteFile(FileManager* aFileManager,
                                       int64_t aFileId) {
   AssertIsOnBackgroundThread();
+
+  if (mShutdownRequested) {
+    // Whoops! We want to delete an IndexedDB disk-backed File but it's too late
+    // to actually delete the file! This means we're going to "leak" the file
+    // and leave it around when we shouldn't! (The file will stay around until
+    // next storage initialization is triggered when the app is started again).
+    // Fixing this is tracked by bug 1539377.
+
+    return NS_OK;
+  }
+
   MOZ_ASSERT(mDeleteTimer);
-
   MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
 
   nsresult rv = mDeleteTimer->InitWithNamedFuncCallback(
       DeleteTimerCallback, this, kDeleteTimeoutMs, nsITimer::TYPE_ONE_SHOT,
       "dom::indexeddb::QuotaClient::AsyncDeleteFile");
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1928,16 +1928,21 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * Capture the contents of the current WebRender frame and
    * save them to a folder relative to the current working directory.
    */
   void wrCapture();
 
   /**
+   * Toggle recording of composition on and off.
+   */
+  void setCompositionRecording(in boolean aValue);
+
+  /**
    * Returns whether the document we're associated to has recorded a given CSS
    * property via the use counter mechanism.
    *
    * Throws if there's no document or the property is invalid.
    */
   bool isCssPropertyRecordedInUseCounter(in ACString aProperty);
 
   /**
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -51,17 +51,16 @@ DIRS += [
     'filehandle',
     'filesystem',
     'flex',
     'gamepad',
     'geolocation',
     'grid',
     'html',
     'jsurl',
-    'asmjscache',
     'mathml',
     'media',
     'midi',
     'notification',
     'offline',
     'power',
     'push',
     'quota',
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -28,17 +28,16 @@
 #include <algorithm>
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/dom/PContent.h"
-#include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/localstorage/ActorsParent.h"
 #include "mozilla/dom/quota/PQuotaParent.h"
 #include "mozilla/dom/quota/PQuotaRequestParent.h"
 #include "mozilla/dom/quota/PQuotaUsageRequestParent.h"
 #include "mozilla/dom/simpledb/ActorsParent.h"
 #include "mozilla/dom/StorageActivityService.h"
@@ -1370,17 +1369,17 @@ void ReportInternalError(const char* aFi
       NS_ConvertUTF8toUTF16(
           nsPrintfCString("Quota %s: %s:%" PRIu32, aStr, aFile, aLine)),
       "quota",
       false /* Quota Manager is not active in private browsing mode */);
 }
 
 namespace {
 
-nsString gBaseDirPath;
+StaticAutoPtr<nsString> gBaseDirPath;
 
 #ifdef DEBUG
 bool gQuotaManagerInitialized = false;
 #endif
 
 StaticRefPtr<QuotaManager> gInstance;
 bool gCreateFailed = false;
 mozilla::Atomic<bool> gShutdown(false);
@@ -2494,36 +2493,49 @@ NS_IMPL_ISUPPORTS(QuotaManager::Observer
 NS_IMETHODIMP
 QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
                                 const char16_t* aData) {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv;
 
   if (!strcmp(aTopic, kProfileDoChangeTopic)) {
+    if (NS_WARN_IF(gBaseDirPath)) {
+      NS_WARNING("profile-before-change-qm must precede repeated "
+                 "profile-do-change!");
+      return NS_OK;
+    }
+
+    gBaseDirPath = new nsString();
+
     nsCOMPtr<nsIFile> baseDir;
     rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
                                 getter_AddRefs(baseDir));
     if (NS_FAILED(rv)) {
       rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                   getter_AddRefs(baseDir));
     }
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    rv = baseDir->GetPath(gBaseDirPath);
+    rv = baseDir->GetPath(*gBaseDirPath);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
+    if (NS_WARN_IF(!gBaseDirPath)) {
+      NS_WARNING("profile-do-change must precede profile-before-change-qm!");
+      return NS_OK;
+    }
+
     // mPendingProfileChange is our re-entrancy guard (the nested event loop
     // below may cause re-entrancy).
     if (mPendingProfileChange) {
       return NS_OK;
     }
 
     AutoRestore<bool> pending(mPendingProfileChange);
     mPendingProfileChange = true;
@@ -2537,17 +2549,17 @@ QuotaManager::Observer::Observe(nsISuppo
     }
 
     if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
       return NS_ERROR_FAILURE;
     }
 
     MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
 
-    gBaseDirPath.Truncate();
+    gBaseDirPath = nullptr;
 
     return NS_OK;
   }
 
   if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
     rv = Shutdown();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
@@ -2900,26 +2912,29 @@ nsresult QuotaManager::Initialize() {
   return NS_OK;
 }
 
 void QuotaManager::GetOrCreate(nsIRunnable* aCallback,
                                nsIEventTarget* aMainEventTarget) {
   AssertIsOnBackgroundThread();
 
   if (IsShuttingDown()) {
-    MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
+    MOZ_ASSERT(false, "Calling QuotaManager::GetOrCreate() after shutdown!");
     return;
   }
 
-  if (gInstance || gCreateFailed) {
+  if (NS_WARN_IF(!gBaseDirPath)) {
+    NS_WARNING("profile-do-change must precede QuotaManager::GetOrCreate()");
+    MOZ_ASSERT(!gInstance);
+  } else if (gInstance || gCreateFailed) {
     MOZ_ASSERT_IF(gCreateFailed, !gInstance);
   } else {
     RefPtr<QuotaManager> manager = new QuotaManager();
 
-    nsresult rv = manager->Init(gBaseDirPath);
+    nsresult rv = manager->Init(*gBaseDirPath);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       gCreateFailed = true;
     } else {
       gInstance = manager;
     }
   }
 
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
@@ -3281,27 +3296,25 @@ nsresult QuotaManager::Init(const nsAStr
 
   // Make a timer here to avoid potential failures later. We don't actually
   // initialize the timer until shutdown.
   mShutdownTimer = NS_NewTimer();
   if (NS_WARN_IF(!mShutdownTimer)) {
     return NS_ERROR_FAILURE;
   }
 
-  static_assert(Client::IDB == 0 && Client::ASMJS == 1 &&
-                    Client::DOMCACHE == 2 && Client::SDB == 3 &&
-                    Client::LS == 4 && Client::TYPE_MAX == 5,
+  static_assert(Client::IDB == 0 && Client::DOMCACHE == 1 && Client::SDB == 2 &&
+                    Client::LS == 3 && Client::TYPE_MAX == 4,
                 "Fix the registration!");
 
   MOZ_ASSERT(mClients.Capacity() == Client::TYPE_MAX,
              "Should be using an auto array with correct capacity!");
 
   // Register clients.
   mClients.AppendElement(indexedDB::CreateQuotaClient());
-  mClients.AppendElement(asmjscache::CreateClient());
   mClients.AppendElement(cache::CreateQuotaClient());
   mClients.AppendElement(simpledb::CreateQuotaClient());
   if (NextGenLocalStorageEnabled()) {
     mClients.AppendElement(localstorage::CreateQuotaClient());
   } else {
     mClients.SetLength(Client::TypeMax());
   }
 
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -38,17 +38,16 @@ class Client {
  public:
   typedef mozilla::Atomic<bool> AtomicBool;
 
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   enum Type {
     IDB = 0,
     // APPCACHE,
-    ASMJS,
     DOMCACHE,
     SDB,
     LS,
     TYPE_MAX
   };
 
   static Type TypeMax() {
     if (CachedNextGenLocalStorageEnabled()) {
new file mode 100644
--- /dev/null
+++ b/gfx/layers/CompositionRecorder.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositionRecorder.h"
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPrefs.h"
+
+#include <ctime>
+#include <iomanip>
+#include "stdio.h"
+#ifdef XP_WIN
+#  include "direct.h"
+#else
+#  include <sys/types.h>
+#  include "sys/stat.h"
+#endif
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace layers {
+
+CompositionRecorder::CompositionRecorder(TimeStamp aRecordingStart)
+    : mRecordingStart(aRecordingStart) {}
+
+CompositionRecorder::~CompositionRecorder() {}
+
+void CompositionRecorder::RecordFrame(RecordedFrame* aFrame) {
+  mCollectedFrames.AppendElement(aFrame);
+}
+
+void CompositionRecorder::WriteCollectedFrames() {
+  std::time_t t = std::time(nullptr);
+  std::tm tm = *std::localtime(&t);
+  std::stringstream str;
+  str << gfxPrefs::LayersWindowRecordingPath() << "windowrecording-"
+      << std::put_time(&tm, "%Y%m%d%H%M%S");
+
+#ifdef XP_WIN
+  _mkdir(str.str().c_str());
+#else
+  mkdir(str.str().c_str(), 0777);
+#endif
+
+  uint32_t i = 1;
+  for (RefPtr<RecordedFrame>& frame : mCollectedFrames) {
+    RefPtr<DataSourceSurface> surf = frame->GetSourceSurface();
+    std::stringstream filename;
+    filename << str.str() << "/frame-" << i << "-"
+             << uint32_t(
+                    (frame->GetTimeStamp() - mRecordingStart).ToMilliseconds())
+             << ".png";
+    gfxUtils::WriteAsPNG(surf, filename.str().c_str());
+    i++;
+  }
+  mCollectedFrames.Clear();
+}
+
+}  // namespace layers
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/CompositionRecorder.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_CompositionRecorder_h
+#define mozilla_layers_CompositionRecorder_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+namespace layers {
+
+class RecordedFrame {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RecordedFrame)
+
+  // The resulting DataSourceSurface must not be kept alive beyond the lifetime
+  // of the RecordedFrame object, since it may refer to data owned by the frame.
+  virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() = 0;
+  TimeStamp GetTimeStamp() { return mTimeStamp; }
+
+ protected:
+  virtual ~RecordedFrame() {}
+  RecordedFrame(const TimeStamp& aTimeStamp) : mTimeStamp(aTimeStamp) {}
+
+ private:
+  TimeStamp mTimeStamp;
+};
+
+/**
+ *
+ */
+class CompositionRecorder final {
+ public:
+  explicit CompositionRecorder(TimeStamp aRecordingStart);
+  ~CompositionRecorder();
+
+  void RecordFrame(RecordedFrame* aFrame);
+
+  void WriteCollectedFrames();
+
+ private:
+  nsTArray<RefPtr<RecordedFrame>> mCollectedFrames;
+  TimeStamp mRecordingStart;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_ProfilerScreenshots_h
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -410,17 +410,18 @@ class CompositableHandle {
 MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollDirection, uint32_t, (
   eVertical,
   eHorizontal
 ));
 
 MOZ_DEFINE_ENUM_CLASS_WITH_BASE(CompositionPayloadType, uint8_t, (
   eKeyPress,
   eAPZScroll,
-  eAPZPinchZoom
+  eAPZPinchZoom,
+  eContentPaint
 ));
 // clang-format on
 
 struct CompositionPayload {
   bool operator==(const CompositionPayload& aOther) const {
     return mType == aOther.mType && mTimeStamp == aOther.mTimeStamp;
   }
   /* The type of payload that is in this composition */
--- a/gfx/layers/apz/public/IAPZCTreeManager.h
+++ b/gfx/layers/apz/public/IAPZCTreeManager.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_IAPZCTreeManager_h
 #define mozilla_layers_IAPZCTreeManager_h
 
 #include <stdint.h>  // for uint64_t, uint32_t
 
+#include "mozilla/layers/APZTypes.h"
 #include "mozilla/layers/LayersTypes.h"          // for TouchBehaviorFlags
 #include "mozilla/layers/ScrollableLayerGuid.h"  // for ScrollableLayerGuid, etc
 #include "mozilla/layers/ZoomConstraints.h"      // for ZoomConstraints
 #include "nsTArrayForwardDeclare.h"  // for nsTArray, nsTArray_Impl, etc
 #include "nsISupportsImpl.h"         // for MOZ_COUNT_CTOR, etc
 #include "Units.h"                   // for CSSRect, etc
 
 namespace mozilla {
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -366,16 +366,19 @@ void ClientLayerManager::EndTransaction(
     mInTransaction = false;
     return;
   }
 
   if (mWidget) {
     mWidget->PrepareWindowEffects();
   }
   EndTransactionInternal(aCallback, aCallbackData, aFlags);
+  if (XRE_IsContentProcess()) {
+    RegisterPayload({CompositionPayloadType::eContentPaint, TimeStamp::Now()});
+  }
   ForwardTransaction(!(aFlags & END_NO_REMOTE_COMPOSITE));
 
   if (mRepeatTransaction) {
     mRepeatTransaction = false;
     mIsRepeatTransaction = true;
 
     // BeginTransaction will reset the transaction start time, but we
     // would like to keep the original time for telemetry purposes.
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -48,16 +48,17 @@ namespace gfx {
 class DrawTarget;
 }  // namespace gfx
 
 namespace layers {
 
 class CanvasLayerComposite;
 class ColorLayerComposite;
 class Compositor;
+class CompositionRecorder;
 class ContainerLayerComposite;
 class Diagnostics;
 struct EffectChain;
 class ImageLayer;
 class ImageLayerComposite;
 class LayerComposite;
 class RefLayerComposite;
 class PaintedLayerComposite;
@@ -195,29 +196,34 @@ class HostLayerManager : public LayerMan
   // async compositables.
   uint64_t GetCompositorBridgeID() const { return mCompositorBridgeID; }
   void SetCompositorBridgeID(uint64_t aID) {
     MOZ_ASSERT(mCompositorBridgeID == 0,
                "The compositor ID must be set only once.");
     mCompositorBridgeID = aID;
   }
 
+  void SetCompositionRecorder(CompositionRecorder* aRecorder) {
+    mCompositionRecorder = aRecorder;
+  }
+
  protected:
   bool mDebugOverlayWantsNextFrame;
   nsTArray<ImageCompositeNotificationInfo> mImageCompositeNotifications;
   // Testing property. If hardware composer is supported, this will return
   // true if the last frame was deemed 'too complicated' to be rendered.
   float mWarningLevel;
   mozilla::TimeStamp mWarnTime;
   UniquePtr<Diagnostics> mDiagnostics;
   uint64_t mCompositorBridgeID;
 
   bool mWindowOverlayChanged;
   TimeDuration mLastPaintTime;
   TimeStamp mRenderStartTime;
+  CompositionRecorder* mCompositionRecorder = nullptr;
 
   // Render time for the current composition.
   TimeStamp mCompositionTime;
 
   // When nonnull, during rendering, some compositable indicated that it will
   // change its rendering at this time. In order not to miss it, we composite
   // on every vsync until this time occurs (this is the latest such time).
   TimeStamp mCompositeUntilTime;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -40,16 +40,17 @@
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/layers/AnimationHelper.h"  // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZSampler.h"             // for APZSampler
 #include "mozilla/layers/APZThreadUtils.h"         // for APZThreadUtils
 #include "mozilla/layers/APZUpdater.h"             // for APZUpdater
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"          // for BasicCompositor
+#include "mozilla/layers/CompositionRecorder.h"      // for CompositionRecorder
 #include "mozilla/layers/Compositor.h"               // for Compositor
 #include "mozilla/layers/CompositorManagerParent.h"  // for CompositorManagerParent
 #include "mozilla/layers/CompositorOGL.h"            // for CompositorOGL
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/CompositorVsyncScheduler.h"
 #include "mozilla/layers/ContentCompositorBridgeParent.h"
 #include "mozilla/layers/FrameUniformityData.h"
@@ -2594,10 +2595,24 @@ int32_t RecordContentFrameTime(
                             fracLatencyNorm);
     }
     return result;
   }
 
   return 0;
 }
 
+mozilla::ipc::IPCResult CompositorBridgeParent::RecvBeginRecording(
+    const TimeStamp& aRecordingStart) {
+  mCompositionRecorder.reset(new CompositionRecorder(aRecordingStart));
+  mLayerManager->SetCompositionRecorder(mCompositionRecorder.get());
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorBridgeParent::RecvEndRecording() {
+  mLayerManager->SetCompositionRecorder(nullptr);
+  mCompositionRecorder->WriteCollectedFrames();
+  mCompositionRecorder.reset(nullptr);
+  return IPC_OK();
+}
+
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -67,16 +67,17 @@ class ProtocolFuzzerHelper;
 namespace layers {
 
 class APZCTreeManager;
 class APZCTreeManagerParent;
 class APZSampler;
 class APZUpdater;
 class AsyncCompositionManager;
 class AsyncImagePipelineManager;
+class CompositionRecorder;
 class Compositor;
 class CompositorAnimationStorage;
 class CompositorBridgeParent;
 class CompositorManagerParent;
 class CompositorVsyncScheduler;
 class HostLayerManager;
 class IAPZCTreeManager;
 class LayerTransactionParent;
@@ -233,16 +234,19 @@ class CompositorBridgeParentBase : publi
   virtual mozilla::ipc::IPCResult RecvRemotePluginsReady() = 0;
   virtual mozilla::ipc::IPCResult RecvAdoptChild(const LayersId& id) = 0;
   virtual mozilla::ipc::IPCResult RecvFlushRenderingAsync() = 0;
   virtual mozilla::ipc::IPCResult RecvForcePresent() = 0;
   virtual mozilla::ipc::IPCResult RecvNotifyRegionInvalidated(
       const nsIntRegion& region) = 0;
   virtual mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() = 0;
   virtual mozilla::ipc::IPCResult RecvAllPluginsCaptured() = 0;
+  virtual mozilla::ipc::IPCResult RecvBeginRecording(
+      const TimeStamp& aRecordingStart) = 0;
+  virtual mozilla::ipc::IPCResult RecvEndRecording() = 0;
   virtual mozilla::ipc::IPCResult RecvInitialize(
       const LayersId& rootLayerTreeId) = 0;
   virtual mozilla::ipc::IPCResult RecvGetFrameUniformity(
       FrameUniformityData* data) = 0;
   virtual mozilla::ipc::IPCResult RecvWillClose() = 0;
   virtual mozilla::ipc::IPCResult RecvPause() = 0;
   virtual mozilla::ipc::IPCResult RecvResume() = 0;
   virtual mozilla::ipc::IPCResult RecvNotifyChildCreated(
@@ -338,16 +342,19 @@ class CompositorBridgeParent final : pub
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see ContentCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
   mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint() override {
     return IPC_OK();
   };
 
   mozilla::ipc::IPCResult RecvAllPluginsCaptured() override;
+  mozilla::ipc::IPCResult RecvBeginRecording(
+      const TimeStamp& aRecordingStart) override;
+  mozilla::ipc::IPCResult RecvEndRecording() override;
 
   virtual void NotifyMemoryPressure() override;
   virtual void AccumulateMemoryReport(wr::MemoryReport*) override;
 
   void ActorDestroy(ActorDestroyReason why) override;
 
   void ShadowLayersUpdated(LayerTransactionParent* aLayerTree,
                            const TransactionInfo& aInfo,
@@ -757,16 +764,17 @@ class CompositorBridgeParent final : pub
   RefPtr<APZUpdater> mApzUpdater;
 
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
   // This makes sure the compositorParent is not destroyed before receiving
   // confirmation that the channel is closed.
   // mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy.
   RefPtr<CompositorBridgeParent> mSelfRef;
   RefPtr<CompositorAnimationStorage> mAnimationStorage;
+  UniquePtr<CompositionRecorder> mCompositionRecorder;
 
   TimeDuration mPaintTime;
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   // cached plugin data used to reduce the number of updates we request.
   LayersId mLastPluginUpdateLayerTreeId;
   nsIntPoint mPluginsLayerOffset;
   nsIntRegion mPluginsLayerVisibleRegion;
--- a/gfx/layers/ipc/ContentCompositorBridgeParent.h
+++ b/gfx/layers/ipc/ContentCompositorBridgeParent.h
@@ -79,16 +79,22 @@ class ContentCompositorBridgeParent fina
     return IPC_OK();
   }
 
   mozilla::ipc::IPCResult RecvCheckContentOnlyTDR(
       const uint32_t& sequenceNum, bool* isContentOnlyTDR) override;
 
   mozilla::ipc::IPCResult RecvAllPluginsCaptured() override { return IPC_OK(); }
 
+  mozilla::ipc::IPCResult RecvBeginRecording(
+      const TimeStamp& aRecordingStart) override {
+    return IPC_OK();
+  }
+  mozilla::ipc::IPCResult RecvEndRecording() override { return IPC_OK(); }
+
   mozilla::ipc::IPCResult RecvGetFrameUniformity(
       FrameUniformityData* aOutData) override {
     // Don't support calculating frame uniformity on the child process and
     // this is just a stub for now.
     MOZ_ASSERT(false);
     return IPC_OK();
   }
 
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -259,16 +259,19 @@ parent:
   sync SyncWithCompositor();
 
   // The pipelineId is the same as the layersId
   async PWebRenderBridge(PipelineId pipelineId, LayoutDeviceIntSize aSize);
 
   sync CheckContentOnlyTDR(uint32_t sequenceNum)
     returns (bool isContentOnlyTDR);
 
+  async BeginRecording(TimeStamp aRecordingStart);
+  async EndRecording();
+
 child:
   // Send back Compositor Frame Metrics from APZCs so tiled layers can
   // update progressively.
   async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, LayersId aLayersId, uint32_t aAPZCId);
   async ReleaseSharedCompositorFrameMetrics(ViewID aId, uint32_t aAPZCId);
   async NotifyWebRenderError(WebRenderError error);
 };
 
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.cpp
@@ -17,16 +17,17 @@
 #include "ShaderDefinitionsMLGPU.h"
 #include "SharedBufferMLGPU.h"
 #include "UnitTransforms.h"
 #include "TextureSourceProviderMLGPU.h"
 #include "TreeTraversal.h"
 #include "FrameBuilder.h"
 #include "LayersLogging.h"
 #include "UtilityMLGPU.h"
+#include "CompositionRecorder.h"
 #include "mozilla/layers/Diagnostics.h"
 #include "mozilla/layers/TextRenderer.h"
 
 #ifdef XP_WIN
 #  include "mozilla/widget/WinCompositorWidget.h"
 #  include "mozilla/gfx/DeviceManagerDx.h"
 #endif
 
@@ -37,16 +38,59 @@ namespace layers {
 
 using namespace gfx;
 
 static const int kDebugOverlayX = 2;
 static const int kDebugOverlayY = 5;
 static const int kDebugOverlayMaxWidth = 600;
 static const int kDebugOverlayMaxHeight = 96;
 
+class RecordedFrameMLGPU : public RecordedFrame {
+ public:
+  RecordedFrameMLGPU(MLGDevice* aDevice, MLGTexture* aTexture,
+                     const TimeStamp& aTimestamp)
+      : RecordedFrame(aTimestamp), mDevice(aDevice) {
+    mSoftTexture =
+        aDevice->CreateTexture(aTexture->GetSize(), SurfaceFormat::B8G8R8A8,
+                               MLGUsage::Staging, MLGTextureFlags::None);
+
+    aDevice->CopyTexture(mSoftTexture, IntPoint(), aTexture,
+                         IntRect(IntPoint(), aTexture->GetSize()));
+  }
+
+  ~RecordedFrameMLGPU() {
+    if (mIsMapped) {
+      mDevice->Unmap(mSoftTexture);
+    }
+  }
+
+  virtual already_AddRefed<gfx::DataSourceSurface> GetSourceSurface() override {
+    if (mDataSurf) {
+      return RefPtr<DataSourceSurface>(mDataSurf).forget();
+    }
+    MLGMappedResource map;
+    if (!mDevice->Map(mSoftTexture, MLGMapType::READ, &map)) {
+      return nullptr;
+    }
+
+    mIsMapped = true;
+    mDataSurf = Factory::CreateWrappingDataSourceSurface(
+        map.mData, map.mStride, mSoftTexture->GetSize(),
+        SurfaceFormat::B8G8R8A8);
+    return RefPtr<DataSourceSurface>(mDataSurf).forget();
+  }
+
+ private:
+  RefPtr<MLGDevice> mDevice;
+  // Software texture in VRAM.
+  RefPtr<MLGTexture> mSoftTexture;
+  RefPtr<DataSourceSurface> mDataSurf;
+  bool mIsMapped = false;
+};
+
 LayerManagerMLGPU::LayerManagerMLGPU(widget::CompositorWidget* aWidget)
     : mWidget(aWidget),
       mDrawDiagnostics(false),
       mUsingInvalidation(false),
       mCurrentFrame(nullptr),
       mDebugFrameNumber(0) {
   if (!aWidget) {
     return;
@@ -351,16 +395,32 @@ void LayerManagerMLGPU::RenderLayers() {
     mDevice->StartDiagnostics(numPixels);
   }
 
   // Execute all render passes.
   builder.Render();
 
   mProfilerScreenshotGrabber.MaybeGrabScreenshot(
       mDevice, builder.GetWidgetRT()->GetTexture());
+
+  if (mCompositionRecorder) {
+    bool hasContentPaint = false;
+    for (CompositionPayload& payload : mPayload) {
+      if (payload.mType == CompositionPayloadType::eContentPaint) {
+        hasContentPaint = true;
+        break;
+      }
+    }
+
+    if (hasContentPaint) {
+      RefPtr<RecordedFrame> frame = new RecordedFrameMLGPU(
+          mDevice, builder.GetWidgetRT()->GetTexture(), TimeStamp::Now());
+      mCompositionRecorder->RecordFrame(frame);
+    }
+  }
   mCurrentFrame = nullptr;
 
   if (mDrawDiagnostics) {
     mDiagnostics->RecordCompositeTime(
         (TimeStamp::Now() - start).ToMilliseconds());
     mDevice->EndDiagnostics();
   }
 }
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -156,16 +156,17 @@ EXPORTS.mozilla.layers += [
     'composite/ImageComposite.h',
     'composite/ImageHost.h',
     'composite/ImageLayerComposite.h',
     'composite/LayerManagerComposite.h',
     'composite/PaintedLayerComposite.h',
     'composite/TextRenderer.h',
     'composite/TextureHost.h',
     'composite/TiledContentHost.h',
+    'CompositionRecorder.h',
     'Compositor.h',
     'CompositorTypes.h',
     'CopyableCanvasRenderer.h',
     'D3D11ShareHandleImage.h',
     'D3D11YCbCrImage.h',
     'D3D9SurfaceImage.h',
     'DirectionUtils.h',
     'Effects.h',
@@ -404,16 +405,17 @@ UNIFIED_SOURCES += [
     'composite/ImageComposite.cpp',
     'composite/ImageHost.cpp',
     'composite/ImageLayerComposite.cpp',
     'composite/LayerManagerComposite.cpp',
     'composite/PaintedLayerComposite.cpp',
     'composite/TextRenderer.cpp',
     'composite/TextureHost.cpp',
     'composite/TiledContentHost.cpp',
+    'CompositionRecorder.cpp',
     'Compositor.cpp',
     'CopyableCanvasRenderer.cpp',
     'Effects.cpp',
     'FrameMetrics.cpp',
     'GLImages.cpp',
     'ImageDataSerializer.cpp',
     'ImageLayers.cpp',
     'ipc/APZChild.cpp',
--- a/gfx/layers/opengl/GLManager.h
+++ b/gfx/layers/opengl/GLManager.h
@@ -2,17 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_GFX_GLMANAGER_H
 #define MOZILLA_GFX_GLMANAGER_H
 
+#include "GLDefs.h"
 #include "mozilla/gfx/Types.h"  // for SurfaceFormat
+#include "mozilla/gfx/2D.h"
 
 namespace mozilla {
 namespace gl {
 class GLContext;
 }  // namespace gl
 
 namespace layers {
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -664,16 +664,17 @@ class gfxPrefs final {
   DECL_GFX_PREF(Once, "layers.omtp.paint-workers",             LayersOMTPPaintWorkers, int32_t, 1);
   DECL_GFX_PREF(Live, "layers.omtp.release-capture-on-main-thread", LayersOMTPReleaseCaptureOnMainThread, bool, false);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, true);
+  DECL_GFX_PREF(Once, "layers.windowrecording.path",           LayersWindowRecordingPath, std::string, std::string());
 
   // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
   // screen size does not align nicely to the default tile size. Although layers can be any size,
   // they are often the same size as the screen, especially for width.
   DECL_GFX_PREF(Once, "layers.tile-width",                     LayersTileWidth, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-height",                    LayersTileHeight, int32_t, 256);
   DECL_GFX_PREF(Once, "layers.tile-initial-pool-size",         LayersTileInitialPoolSize, uint32_t, (uint32_t)50);
   DECL_GFX_PREF(Once, "layers.tile-pool-unused-size",          LayersTilePoolUnusedSize, uint32_t, (uint32_t)10);
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -19,17 +19,16 @@
 #include "mozilla/dom/PBackgroundLSDatabaseChild.h"
 #include "mozilla/dom/PBackgroundLSObserverChild.h"
 #include "mozilla/dom/PBackgroundLSRequestChild.h"
 #include "mozilla/dom/PBackgroundLSSimpleRequestChild.h"
 #include "mozilla/dom/PBackgroundSDBConnectionChild.h"
 #include "mozilla/dom/PFileSystemRequestChild.h"
 #include "mozilla/dom/EndpointForReportChild.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
-#include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamChild.h"
 #include "mozilla/dom/ipc/PendingIPCBlobChild.h"
 #include "mozilla/dom/ipc/TemporaryIPCBlobChild.h"
 #include "mozilla/dom/quota/PQuotaChild.h"
 #include "mozilla/dom/RemoteWorkerChild.h"
@@ -84,17 +83,16 @@ namespace ipc {
 using mozilla::dom::UDPSocketChild;
 using mozilla::net::PUDPSocketChild;
 
 using mozilla::dom::LocalStorage;
 using mozilla::dom::PServiceWorkerChild;
 using mozilla::dom::PServiceWorkerContainerChild;
 using mozilla::dom::PServiceWorkerRegistrationChild;
 using mozilla::dom::StorageDBChild;
-using mozilla::dom::asmjscache::PAsmJSCacheEntryChild;
 using mozilla::dom::cache::PCacheChild;
 using mozilla::dom::cache::PCacheStorageChild;
 using mozilla::dom::cache::PCacheStreamControlChild;
 
 using mozilla::dom::WebAuthnTransactionChild;
 
 using mozilla::dom::PMIDIManagerChild;
 using mozilla::dom::PMIDIPortChild;
@@ -567,31 +565,16 @@ BackgroundChildImpl::AllocPParentToChild
 }
 
 bool BackgroundChildImpl::DeallocPParentToChildStreamChild(
     PParentToChildStreamChild* aActor) {
   delete aActor;
   return true;
 }
 
-PAsmJSCacheEntryChild* BackgroundChildImpl::AllocPAsmJSCacheEntryChild(
-    const dom::asmjscache::OpenMode& aOpenMode,
-    const dom::asmjscache::WriteParams& aWriteParams,
-    const PrincipalInfo& aPrincipalInfo) {
-  MOZ_CRASH("PAsmJSCacheEntryChild actors should be manually constructed!");
-}
-
-bool BackgroundChildImpl::DeallocPAsmJSCacheEntryChild(
-    PAsmJSCacheEntryChild* aActor) {
-  MOZ_ASSERT(aActor);
-
-  dom::asmjscache::DeallocEntryChild(aActor);
-  return true;
-}
-
 BackgroundChildImpl::PQuotaChild* BackgroundChildImpl::AllocPQuotaChild() {
   MOZ_CRASH("PQuotaChild actor should be manually constructed!");
 }
 
 bool BackgroundChildImpl::DeallocPQuotaChild(PQuotaChild* aActor) {
   MOZ_ASSERT(aActor);
   delete aActor;
   return true;
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -208,24 +208,16 @@ class BackgroundChildImpl : public PBack
   virtual bool DeallocPChildToParentStreamChild(
       PChildToParentStreamChild* aActor) override;
 
   virtual PParentToChildStreamChild* AllocPParentToChildStreamChild() override;
 
   virtual bool DeallocPParentToChildStreamChild(
       PParentToChildStreamChild* aActor) override;
 
-  virtual PAsmJSCacheEntryChild* AllocPAsmJSCacheEntryChild(
-      const dom::asmjscache::OpenMode& aOpenMode,
-      const dom::asmjscache::WriteParams& aWriteParams,
-      const PrincipalInfo& aPrincipalInfo) override;
-
-  virtual bool DeallocPAsmJSCacheEntryChild(
-      PAsmJSCacheEntryChild* aActor) override;
-
   virtual PQuotaChild* AllocPQuotaChild() override;
 
   virtual bool DeallocPQuotaChild(PQuotaChild* aActor) override;
 
   virtual PFileSystemRequestChild* AllocPFileSystemRequestChild(
       const FileSystemParams&) override;
 
   virtual bool DeallocPFileSystemRequestChild(
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -23,17 +23,16 @@
 #include "mozilla/dom/GamepadTestChannelParent.h"
 #include "mozilla/dom/PGamepadEventChannelParent.h"
 #include "mozilla/dom/PGamepadTestChannelParent.h"
 #include "mozilla/dom/MessagePortParent.h"
 #include "mozilla/dom/ServiceWorkerActors.h"
 #include "mozilla/dom/ServiceWorkerManagerParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/StorageActivityService.h"
-#include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamParent.h"
 #include "mozilla/dom/ipc/PendingIPCBlobParent.h"
 #include "mozilla/dom/ipc/TemporaryIPCBlobParent.h"
 #include "mozilla/dom/localstorage/ActorsParent.h"
 #include "mozilla/dom/quota/ActorsParent.h"
 #include "mozilla/dom/simpledb/ActorsParent.h"
@@ -83,17 +82,16 @@ using mozilla::dom::MIDIPortParent;
 using mozilla::dom::PMessagePortParent;
 using mozilla::dom::PMIDIManagerParent;
 using mozilla::dom::PMIDIPortParent;
 using mozilla::dom::PServiceWorkerContainerParent;
 using mozilla::dom::PServiceWorkerParent;
 using mozilla::dom::PServiceWorkerRegistrationParent;
 using mozilla::dom::UDPSocketParent;
 using mozilla::dom::WebAuthnTransactionParent;
-using mozilla::dom::asmjscache::PAsmJSCacheEntryParent;
 using mozilla::dom::cache::PCacheParent;
 using mozilla::dom::cache::PCacheStorageParent;
 using mozilla::dom::cache::PCacheStreamControlParent;
 using mozilla::ipc::AssertIsOnBackgroundThread;
 
 namespace {
 
 class TestParent final : public mozilla::ipc::PBackgroundTestParent {
@@ -944,36 +942,16 @@ mozilla::ipc::IPCResult BackgroundParent
   AssertIsOnBackgroundThread();
 
   if (!MessagePortParent::ForceClose(aUUID, aDestinationUUID, aSequenceID)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
-PAsmJSCacheEntryParent* BackgroundParentImpl::AllocPAsmJSCacheEntryParent(
-    const dom::asmjscache::OpenMode& aOpenMode,
-    const dom::asmjscache::WriteParams& aWriteParams,
-    const PrincipalInfo& aPrincipalInfo) {
-  AssertIsInMainOrSocketProcess();
-  AssertIsOnBackgroundThread();
-
-  return dom::asmjscache::AllocEntryParent(aOpenMode, aWriteParams,
-                                           aPrincipalInfo);
-}
-
-bool BackgroundParentImpl::DeallocPAsmJSCacheEntryParent(
-    PAsmJSCacheEntryParent* aActor) {
-  AssertIsInMainOrSocketProcess();
-  AssertIsOnBackgroundThread();
-
-  dom::asmjscache::DeallocEntryParent(aActor);
-  return true;
-}
-
 BackgroundParentImpl::PQuotaParent* BackgroundParentImpl::AllocPQuotaParent() {
   AssertIsInMainOrSocketProcess();
   AssertIsOnBackgroundThread();
 
   return mozilla::dom::quota::AllocPQuotaParent();
 }
 
 bool BackgroundParentImpl::DeallocPQuotaParent(PQuotaParent* aActor) {
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -258,24 +258,16 @@ class BackgroundParentImpl : public PBac
       const nsID& aDestinationUUID, const uint32_t& aSequenceID) override;
 
   virtual bool DeallocPMessagePortParent(PMessagePortParent* aActor) override;
 
   virtual mozilla::ipc::IPCResult RecvMessagePortForceClose(
       const nsID& aUUID, const nsID& aDestinationUUID,
       const uint32_t& aSequenceID) override;
 
-  virtual PAsmJSCacheEntryParent* AllocPAsmJSCacheEntryParent(
-      const dom::asmjscache::OpenMode& aOpenMode,
-      const dom::asmjscache::WriteParams& aWriteParams,
-      const PrincipalInfo& aPrincipalInfo) override;
-
-  virtual bool DeallocPAsmJSCacheEntryParent(
-      PAsmJSCacheEntryParent* aActor) override;
-
   virtual PQuotaParent* AllocPQuotaParent() override;
 
   virtual bool DeallocPQuotaParent(PQuotaParent* aActor) override;
 
   virtual mozilla::ipc::IPCResult RecvShutdownQuotaManager() override;
 
   virtual PFileSystemRequestParent* AllocPFileSystemRequestParent(
       const FileSystemParams&) override;
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -1,13 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-include protocol PAsmJSCacheEntry;
 include protocol PBackgroundIDBFactory;
 include protocol PBackgroundIndexedDBUtils;
 include protocol PBackgroundSDBConnection;
 include protocol PBackgroundLSDatabase;
 include protocol PBackgroundLSObserver;
 include protocol PBackgroundLSRequest;
 include protocol PBackgroundLSSimpleRequest;
 include protocol PBackgroundLocalStorageCache;
@@ -58,28 +57,21 @@ include RemoteWorkerTypes;
 include MIDITypes;
 
 include "mozilla/dom/cache/IPCUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 
 using mozilla::dom::cache::Namespace
   from "mozilla/dom/cache/Types.h";
 
-using mozilla::dom::asmjscache::OpenMode
-  from "mozilla/dom/asmjscache/AsmJSCache.h";
-
-using mozilla::dom::asmjscache::WriteParams
-  from "mozilla/dom/asmjscache/AsmJSCache.h";
-
 namespace mozilla {
 namespace ipc {
 
 sync protocol PBackground
 {
-  manages PAsmJSCacheEntry;
   manages PBackgroundIDBFactory;
   manages PBackgroundIndexedDBUtils;
   manages PBackgroundSDBConnection;
   manages PBackgroundLSDatabase;
   manages PBackgroundLSObserver;
   manages PBackgroundLSRequest;
   manages PBackgroundLSSimpleRequest;
   manages PBackgroundLocalStorageCache;
@@ -180,20 +172,16 @@ parent:
   async PCacheStorage(Namespace aNamespace, PrincipalInfo aPrincipalInfo);
 
   async PMessagePort(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
 
   async PChildToParentStream();
 
   async MessagePortForceClose(nsID uuid, nsID destinationUuid, uint32_t sequenceId);
 
-  async PAsmJSCacheEntry(OpenMode openMode,
-                         WriteParams write,
-                         PrincipalInfo principalInfo);
-
   async PQuota();
 
   async ShutdownQuotaManager();
 
   async PFileSystemRequest(FileSystemParams params);
 
   async PGamepadEventChannel();
 
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -24,17 +24,17 @@
  * write barrier should be invoked whenever a write can cause the set of things
  * traced through by the GC to change. This includes:
  *   - writes to object properties
  *   - writes to array slots
  *   - writes to fields like JSObject::shape_ that we trace through
  *   - writes to fields in private data
  *   - writes to non-markable fields like JSObject::private that point to
  *     markable data
- * The last category is the trickiest. Even though the private pointers does not
+ * The last category is the trickiest. Even though the private pointer does not
  * point to a GC thing, changing the private pointer may change the set of
  * objects that are traced by the GC. Therefore it needs a write barrier.
  *
  * Every barriered write should have the following form:
  *   <pre-barrier>
  *   obj->field = value; // do the actual write
  *   <post-barrier>
  * The pre-barrier is used for incremental GC and the post-barrier is for
@@ -114,19 +114,19 @@
  * minor collection.  Part of the way this is achieved is to only mark the
  * nursery itself; tenured things, which may form the majority of the heap, are
  * not traced through or marked.  This leads to the problem of what to do about
  * tenured objects that have pointers into the nursery: if such things are not
  * marked, they may be discarded while there are still live objects which
  * reference them. The solution is to maintain information about these pointers,
  * and mark their targets when we start a minor collection.
  *
- * The pointers can be thought of as edges in object graph, and the set of edges
- * from the tenured generation into the nursery is know as the remembered set.
- * Post barriers are used to track this remembered set.
+ * The pointers can be thought of as edges in an object graph, and the set of
+ * edges from the tenured generation into the nursery is known as the remembered
+ * set. Post barriers are used to track this remembered set.
  *
  * Whenever a slot which could contain such a pointer is written, we check
  * whether the pointed-to thing is in the nursery (if storeBuffer() returns a
  * buffer).  If so we add the cell into the store buffer, which is the
  * collector's representation of the remembered set.  This means that when we
  * come to do a minor collection we can examine the contents of the store buffer
  * and mark any edge targets that are in the nursery.
  *
--- a/media/libopus/moz.build
+++ b/media/libopus/moz.build
@@ -58,38 +58,34 @@ if CONFIG['OS_ARCH'] == 'SunOS':
 if not CONFIG['MOZ_SAMPLE_TYPE_FLOAT32']:
     DEFINES['FIXED_POINT'] = 1
     DEFINES['DISABLE_FLOAT_API'] = True
 
 LOCAL_INCLUDES += [
     'celt',
     'include',
     'silk',
+    'silk/fixed',
+    'silk/float',
     'src',
 ]
 
 # sources.mozbuild is generated from gen-sources.py when a new libopus is
 # imported.
 include('sources.mozbuild')
 
 UNIFIED_SOURCES += celt_sources
 UNIFIED_SOURCES += silk_sources
 UNIFIED_SOURCES += opus_sources
 SOURCES += opus_nonunified_sources
 
 if CONFIG['MOZ_SAMPLE_TYPE_FLOAT32']:
-    LOCAL_INCLUDES += [
-        'silk/float',
-    ]
     UNIFIED_SOURCES += silk_sources_float
     UNIFIED_SOURCES += opus_sources_float
 else:
-    LOCAL_INCLUDES += [
-        'silk/fixed',
-    ]
     UNIFIED_SOURCES += silk_sources_fixed
 
 if CONFIG['CPU_ARCH'] in ('x86', 'x86_64'):
     DEFINES['OPUS_HAVE_RTCD'] = True
     DEFINES['OPUS_X86_MAY_HAVE_SSE'] = True
     DEFINES['OPUS_X86_MAY_HAVE_SSE2'] = True
     DEFINES['OPUS_X86_MAY_HAVE_SSE4_1'] = True
     DEFINES['OPUS_X86_MAY_HAVE_AVX'] = True
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -527,17 +527,17 @@ var BrowserApp = {
       InitLater(() => AsyncPrefs.init());
 
       // Collect telemetry data.
       // We do this at startup because we want to move away from "gather-telemetry" (bug 1127907)
       InitLater(() => {
         Telemetry.addData("FENNEC_TRACKING_PROTECTION_STATE", parseInt(BrowserApp.getTrackingProtectionState()));
       });
 
-      InitLater(() => LightWeightThemeWebInstaller.init());
+      InitLater(() => LightWeightThemeStuff.init());
       InitLater(() => CastingApps.init(), window, "CastingApps");
 
       // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
       InitLater(() => SafeBrowsing.init(), window, "SafeBrowsing");
 
       // Start Marionette after all startup scripts have been run.
       InitLater(() => {
         Services.obs.notifyObservers(window, "marionette-startup-requested");
@@ -3269,153 +3269,48 @@ ChromeUtils.defineModuleGetter(this, "Pa
     Cu.reportError(err);
 
     let sandbox = {};
     ChromeUtils.import(script, sandbox);
     return sandbox[exprt];
   });
 });
 
-var LightWeightThemeWebInstaller = {
+var LightWeightThemeStuff = {
   init: function sh_init() {
-    let temp = {};
-    ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm", temp);
-    let theme = new temp.LightweightThemeConsumer(document);
-    BrowserApp.deck.addEventListener("InstallBrowserTheme", this, false, true);
-    BrowserApp.deck.addEventListener("PreviewBrowserTheme", this, false, true);
-    BrowserApp.deck.addEventListener("ResetBrowserThemePreview", this, false, true);
+    let {LightweightThemeConsumer} =
+        ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm");
+    new LightweightThemeConsumer(document);
 
     if (ParentalControls.parentalControlsEnabled &&
         !this._manager.currentTheme &&
         ParentalControls.isAllowed(ParentalControls.DEFAULT_THEME)) {
       // We are using the DEFAULT_THEME restriction to differentiate between restricted profiles & guest mode - Bug 1199596
       this._installParentalControlsTheme();
     }
   },
 
-  handleEvent: function (event) {
-    switch (event.type) {
-      case "InstallBrowserTheme":
-      case "PreviewBrowserTheme":
-      case "ResetBrowserThemePreview":
-        // ignore requests from background tabs
-        if (event.target.ownerGlobal.top != content)
-          return;
-    }
-
-    switch (event.type) {
-      case "InstallBrowserTheme":
-        this._installRequest(event);
-        break;
-      case "PreviewBrowserTheme":
-        this._preview(event);
-        break;
-      case "ResetBrowserThemePreview":
-        this._resetPreview(event);
-        break;
-      case "pagehide":
-      case "TabSelect":
-        this._resetPreview();
-        break;
-    }
-  },
-
   get _manager () {
-    let temp = {};
-    ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
+    let {LightweightThemeManager} =
+        ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
     delete this._manager;
-    return this._manager = temp.LightweightThemeManager;
+    return this._manager = LightweightThemeManager;
   },
 
   _installParentalControlsTheme: function() {
     let mgr = this._manager;
     let parentalControlsTheme = {
       "headerURL": "resource://android/assets/parental_controls_theme.png",
       "name": "Parental Controls Theme",
       "id": "parental-controls-theme@mozilla.org"
     };
 
     mgr.addBuiltInTheme(parentalControlsTheme);
     mgr.themeChanged(parentalControlsTheme);
   },
-
-  _installRequest: function (event) {
-    let node = event.target;
-    let data = this._getThemeFromNode(node);
-    if (!data)
-      return;
-
-    if (this._isAllowed(node)) {
-      this._install(data);
-      return;
-    }
-
-    let allowButtonText = Strings.browser.GetStringFromName("lwthemeInstallRequest.allowButton");
-    let IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
-    let hostname = IDNService.convertToDisplayIDN(node.ownerDocument.location.hostname, {});
-    let message = Strings.browser.formatStringFromName("lwthemeInstallRequest.message", [hostname], 1);
-    let buttons = [{
-      label: allowButtonText,
-      callback: function () {
-        LightWeightThemeWebInstaller._install(data);
-      },
-      positive: true
-    }];
-
-    NativeWindow.doorhanger.show(message, "Personas", buttons, BrowserApp.selectedTab.id);
-  },
-
-  _install: function (newLWTheme) {
-    this._manager.currentTheme = newLWTheme;
-  },
-
-  _previewWindow: null,
-  _preview: function (event) {
-    if (!this._isAllowed(event.target))
-      return;
-    let data = this._getThemeFromNode(event.target);
-    if (!data)
-      return;
-    this._resetPreview();
-
-    this._previewWindow = event.target.ownerGlobal;
-    this._previewWindow.addEventListener("pagehide", this, true);
-    BrowserApp.deck.addEventListener("TabSelect", this);
-    this._manager.previewTheme(data);
-  },
-
-  _resetPreview: function (event) {
-    if (!this._previewWindow ||
-        event && !this._isAllowed(event.target))
-      return;
-
-    this._previewWindow.removeEventListener("pagehide", this, true);
-    this._previewWindow = null;
-    BrowserApp.deck.removeEventListener("TabSelect", this);
-
-    this._manager.resetPreview();
-  },
-
-  _isAllowed: function (node) {
-    // Make sure the whitelist has been imported to permissions
-    PermissionsUtils.importFromPrefs("xpinstall.", "install");
-
-    let pm = Services.perms;
-
-    let uri = node.ownerDocument.documentURIObject;
-    if (!uri.schemeIs("https")) {
-      return false;
-    }
-
-    return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
-  },
-
-  _getThemeFromNode: function (node) {
-    return this._manager.parseTheme(node.getAttribute("data-browsertheme"), node.baseURI);
-  }
 };
 
 var DesktopUserAgent = {
   DESKTOP_UA: null,
   TCO_DOMAIN: "t.co",
   TCO_REPLACE: / Gecko.*/,
 
   init: function ua_init() {
--- a/services/sync/modules/engines/prefs.js
+++ b/services/sync/modules/engines/prefs.js
@@ -10,19 +10,16 @@ const {XPCOMUtils} = ChromeUtils.import(
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 const {Store, SyncEngine, Tracker} = ChromeUtils.import("resource://services-sync/engines.js");
 const {CryptoWrapper} = ChromeUtils.import("resource://services-sync/record.js");
 const {Svc, Utils} = ChromeUtils.import("resource://services-sync/util.js");
 const {SCORE_INCREMENT_XLARGE} = ChromeUtils.import("resource://services-sync/constants.js");
 const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
 
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
-                          "resource://gre/modules/LightweightThemeManager.jsm");
-
 XPCOMUtils.defineLazyGetter(this, "PREFS_GUID",
                             () => CommonUtils.encodeBase64URL(Services.appinfo.ID));
 
 function PrefRec(collection, id) {
   CryptoWrapper.call(this, collection, id);
 }
 PrefRec.prototype = {
   __proto__: CryptoWrapper.prototype,
@@ -122,68 +119,42 @@ PrefStore.prototype = {
       if (this._isSynced(pref) && !isUnsyncableURLPref(pref)) {
         // Missing and default prefs get the null value.
         values[pref] = this._prefs.isSet(pref) ? this._prefs.get(pref, null) : null;
       }
     }
     return values;
   },
 
-  _updateLightWeightTheme(themeID) {
-    let themeObject = null;
-    if (themeID) {
-      themeObject = LightweightThemeManager.getUsedTheme(themeID);
-    }
-    LightweightThemeManager.currentTheme = themeObject;
-  },
-
   _setAllPrefs(values) {
-    let selectedThemeIDPref = "lightweightThemes.selectedThemeID";
-    let selectedThemeIDBefore = this._prefs.get(selectedThemeIDPref, null);
-    let selectedThemeIDAfter = selectedThemeIDBefore;
-
     // Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise
     // _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device).
     let prefs = Object.keys(values).sort(a => -a.indexOf(PREF_SYNC_PREFS_PREFIX));
     for (let pref of prefs) {
       if (!this._isSynced(pref)) {
         continue;
       }
 
       let value = values[pref];
       if (typeof value == "string" && UNSYNCABLE_URL_REGEXP.test(value)) {
         this._log.trace(`Skipping incoming unsyncable url for pref: ${pref}`);
         continue;
       }
 
-      switch (pref) {
-        // Some special prefs we don't want to set directly.
-        case selectedThemeIDPref:
-          selectedThemeIDAfter = value;
-          break;
-
-        // default is to just set the pref
-        default:
-          if (value == null) {
-            // Pref has gone missing. The best we can do is reset it.
-            this._prefs.reset(pref);
-          } else {
-            try {
-              this._prefs.set(pref, value);
-            } catch (ex) {
-              this._log.trace(`Failed to set pref: ${pref}`, ex);
-            }
-          }
+      if (value == null) {
+        // Pref has gone missing. The best we can do is reset it.
+        this._prefs.reset(pref);
+      } else {
+        try {
+          this._prefs.set(pref, value);
+        } catch (ex) {
+          this._log.trace(`Failed to set pref: ${pref}`, ex);
+        }
       }
     }
-
-    // Notify the lightweight theme manager if the selected theme has changed.
-    if (selectedThemeIDBefore != selectedThemeIDAfter) {
-      this._updateLightWeightTheme(selectedThemeIDAfter);
-    }
   },
 
   async getAllIDs() {
     /* We store all prefs in just one WBO, with just one GUID */
     let allprefs = {};
     allprefs[PREFS_GUID] = true;
     return allprefs;
   },
--- a/services/sync/tests/unit/test_prefs_store.js
+++ b/services/sync/tests/unit/test_prefs_store.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
 const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 const {PrefRec} = ChromeUtils.import("resource://services-sync/engines/prefs.js");
 const {Service} = ChromeUtils.import("resource://services-sync/service.js");
 
 const PREFS_GUID = CommonUtils.encodeBase64URL(Services.appinfo.ID);
 
 AddonTestUtils.init(this);
 AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
@@ -96,77 +95,19 @@ add_task(async function run_test() {
     Assert.equal(prefs.get("testing.bool"), false);
     Assert.equal(prefs.get("testing.deleteme"), undefined);
     Assert.equal(prefs.get("testing.dont.change"), "Please don't change me.");
     Assert.equal(prefs.get("testing.somepref"), "im a new pref from other device");
     Assert.equal(prefs.get("testing.synced.url"), "https://www.example.com");
     Assert.equal(prefs.get("testing.unsynced.url"), "https://www.example.com/2");
     Assert.equal(Svc.Prefs.get("prefs.sync.testing.somepref"), true);
 
-    _("Enable persona");
-    // Ensure we don't go to the network to fetch personas and end up leaking
-    // stuff.
-    Services.io.offline = true;
-    Assert.equal(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org");
-
-    let persona1 = makePersona();
-    let persona2 = makePersona();
-    let usedThemes = JSON.stringify([persona1, persona2]);
-    record.value = {
-      "lightweightThemes.selectedThemeID": persona1.id,
-      "lightweightThemes.usedThemes": usedThemes,
-    };
-    await store.update(record);
-    Assert.equal(prefs.get("lightweightThemes.selectedThemeID"), persona1.id);
-    Assert.ok(Utils.deepEquals(LightweightThemeManager.currentTheme,
-              persona1));
-
-    _("Disable persona");
-    record.value = {
-      "lightweightThemes.selectedThemeID": null,
-      "lightweightThemes.usedThemes": usedThemes,
-    };
-    await store.update(record);
-    Assert.equal(LightweightThemeManager.currentTheme.id, "default-theme@mozilla.org");
-
     _("Only the current app's preferences are applied.");
     record = new PrefRec("prefs", "some-fake-app");
     record.value = {
       "testing.int": 98,
     };
     await store.update(record);
     Assert.equal(prefs.get("testing.int"), 42);
-
-    _("The light-weight theme preference is handled correctly.");
-    let lastThemeID = undefined;
-    let orig_updateLightWeightTheme = store._updateLightWeightTheme;
-    store._updateLightWeightTheme = function(themeID) {
-      lastThemeID = themeID;
-    };
-    try {
-      record = new PrefRec("prefs", PREFS_GUID);
-      record.value = {
-        "testing.int": 42,
-      };
-      await store.update(record);
-      Assert.ok(lastThemeID === undefined,
-                "should not have tried to change the theme with an unrelated pref.");
-      Services.prefs.setCharPref("lightweightThemes.selectedThemeID", "foo");
-      record.value = {
-        "lightweightThemes.selectedThemeID": "foo",
-      };
-      await store.update(record);
-      Assert.ok(lastThemeID === undefined,
-                "should not have tried to change the theme when the incoming pref matches current value.");
-
-      record.value = {
-        "lightweightThemes.selectedThemeID": "bar",
-      };
-      await store.update(record);
-      Assert.equal(lastThemeID, "bar",
-                   "should have tried to change the theme when the incoming pref was different.");
-    } finally {
-      store._updateLightWeightTheme = orig_updateLightWeightTheme;
-    }
   } finally {
     prefs.resetBranch("");
   }
 });
--- a/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_management_themes.js
@@ -1,14 +1,12 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
-
 const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
 PromiseTestUtils.whitelistRejectionsGlobally(/Message manager disconnected/);
 
 add_task(async function test_management_themes() {
   const TEST_ID = "test_management_themes@tests.mozilla.com";
 
   let theme = ExtensionTestUtils.loadExtension({
     manifest: {
@@ -51,17 +49,17 @@ add_task(async function test_management_
       browser.test.assertEq(info.type, "theme", "addon is theme");
       browser.test.sendMessage("onUninstalled", info.name);
     });
 
     async function getAddon(type) {
       let addons = await browser.management.getAll();
       let themes = addons.filter(addon => addon.type === "theme");
       // We get the 3 built-in themes plus the lwt and our addon.
-      browser.test.assertEq(5, themes.length, "got expected addons");
+      browser.test.assertEq(4, themes.length, "got expected addons");
       // We should also get our test extension.
       let testExtension = addons.find(addon => { return addon.id === TEST_ID; });
       browser.test.assertTrue(!!testExtension,
                               `The extension with id ${TEST_ID} was returned by getAll.`);
       let found;
       for (let addon of themes) {
         browser.test.assertEq(addon.type, "theme", "addon is theme");
         if (type == "theme" && addon.id.includes("temporary-addon")) {
@@ -104,37 +102,18 @@ add_task(async function test_management_
       name: TEST_ID,
       permissions: ["management"],
     },
     background: `(${background})("${TEST_ID}")`,
     useAddonManager: "temporary",
   });
   await extension.startup();
 
-  // Test LWT
-  LightweightThemeManager.currentTheme = {
-    id: "lwt@personas.mozilla.org",
-    version: "1",
-    name: "Bling",
-    description: "SO MUCH BLING!",
-    author: "Pixel Pusher",
-    homepageURL: "http://mochi.test:8888/data/index.html",
-    headerURL: "http://mochi.test:8888/data/header.png",
-    previewURL: "http://mochi.test:8888/data/preview.png",
-    iconURL: "http://mochi.test:8888/data/icon.png",
-    textcolor: Math.random().toString(),
-    accentcolor: Math.random().toString(),
-  };
-  is(await extension.awaitMessage("onInstalled"), "Bling", "LWT installed");
-  is(await extension.awaitMessage("onEnabled"), "Bling", "LWT enabled");
-
   await theme.startup();
   is(await extension.awaitMessage("onInstalled"), "Simple theme test", "webextension theme installed");
-  is(await extension.awaitMessage("onDisabled"), "Bling", "LWT disabled");
-  // no enabled event when installed.
 
   extension.sendMessage("test");
   is(await extension.awaitMessage("onEnabled"), "Default", "default enabled");
   is(await extension.awaitMessage("onDisabled"), "Simple theme test", "addon disabled");
   is(await extension.awaitMessage("onEnabled"), "Simple theme test", "addon enabled");
   is(await extension.awaitMessage("onDisabled"), "Default", "default disabled");
   await extension.awaitMessage("done");
 
--- a/toolkit/components/passwordmgr/test/browser/browser_autocomplete_footer.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_autocomplete_footer.js
@@ -66,22 +66,19 @@ add_task(async function test_autocomplet
     let footer = popup.querySelector(`[originaltype="loginsFooter"]`);
     ok(footer, "Got footer richlistitem");
 
     await TestUtils.waitForCondition(() => {
       return !EventUtils.isHidden(footer);
     }, "Waiting for footer to become visible");
 
     EventUtils.synthesizeMouseAtCenter(footer, {});
-    await TestUtils.waitForCondition(() => {
-      return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
-    }, "Waiting for the password manager dialog to open");
+    let window = await waitForPasswordManagerDialog();
     info("Login dialog was opened");
 
-    let window = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
     await TestUtils.waitForCondition(() => {
       return window.document.getElementById("filter").value == "example.com";
     }, "Waiting for the search string to filter logins");
 
     window.close();
     popup.hidePopup();
   });
 });
@@ -104,22 +101,19 @@ add_task(async function test_autocomplet
       return !EventUtils.isHidden(footer);
     }, "Waiting for footer to become visible");
 
     await EventUtils.synthesizeKey("KEY_ArrowDown");
     await EventUtils.synthesizeKey("KEY_ArrowDown");
     await EventUtils.synthesizeKey("KEY_ArrowDown");
     await EventUtils.synthesizeKey("KEY_Enter");
 
-    await TestUtils.waitForCondition(() => {
-      return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
-    }, "Waiting for the password manager dialog to open");
+    let window = await waitForPasswordManagerDialog();
     info("Login dialog was opened");
 
-    let window = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
     await TestUtils.waitForCondition(() => {
       return window.document.getElementById("filter").value == "example.com";
     }, "Waiting for the search string to filter logins");
 
     window.close();
     popup.hidePopup();
   });
 });
--- a/toolkit/components/passwordmgr/test/browser/browser_openPasswordManager.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_openPasswordManager.js
@@ -3,35 +3,29 @@ const PREF_MANAGEMENT_URI = "signon.mana
 function resetPrefs() {
   Services.prefs.clearUserPref(PREF_MANAGEMENT_URI);
 }
 
 registerCleanupFunction(resetPrefs);
 
 add_task(async function test_noFilter() {
   LoginHelper.openPasswordManager(window);
-  await TestUtils.waitForCondition(() => {
-    return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
-  }, "Waiting for the password manager dialog to open");
-  let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
+  let win = await waitForPasswordManagerDialog();
   ok(win, "Login dialog was opened");
   await BrowserTestUtils.closeWindow(win);
   await TestUtils.waitForCondition(() => {
     return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") === null;
   }, "Waiting for the password manager dialog to close");
 });
 
 add_task(async function test_filter() {
   // Greek IDN for example.test
   let domain = "παράδειγμα.δοκιμή";
   LoginHelper.openPasswordManager(window, domain);
-  await TestUtils.waitForCondition(() => {
-    return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") !== null;
-  }, "Waiting for the password manager dialog to open");
-  let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
+  let win = await waitForPasswordManagerDialog();
   await TestUtils.waitForCondition(() => {
     return win.document.getElementById("filter").value == domain;
   }, "Waiting for the search string to filter logins");
   ok(win, "Login dialog was opened with a domain filter");
   await BrowserTestUtils.closeWindow(win);
   await TestUtils.waitForCondition(() => {
     return Services.wm.getMostRecentWindow("Toolkit:PasswordManager") === null;
   }, "Waiting for the password manager dialog to close");
--- a/toolkit/components/passwordmgr/test/browser/head.js
+++ b/toolkit/components/passwordmgr/test/browser/head.js
@@ -149,8 +149,18 @@ async function checkDoorhangerUsernamePa
   }, "Wait for nsLoginManagerPrompter writeDataToUI()");
   is(document.getElementById("password-notification-username").value, username,
      "Check doorhanger username");
   is(document.getElementById("password-notification-password").value, password,
      "Check doorhanger password");
 }
 
 // End popup notification (doorhanger) functions //
+
+async function waitForPasswordManagerDialog() {
+  let win;
+  await TestUtils.waitForCondition(() => {
+    win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
+    return win && win.document.getElementById("filter");
+  }, "Waiting for the password manager dialog to open");
+
+  return win;
+}
--- a/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/app/TelemetryEnvironment.jsm
@@ -19,18 +19,16 @@ const {AppConstants} = ChromeUtils.impor
 const Utils = TelemetryUtils;
 
 const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AttributionCode",
                                "resource:///modules/AttributionCode.jsm");
 ChromeUtils.defineModuleGetter(this, "ctypes",
                                "resource://gre/modules/ctypes.jsm");
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
-                               "resource://gre/modules/LightweightThemeManager.jsm");
 ChromeUtils.defineModuleGetter(this, "ProfileAge",
                                "resource://gre/modules/ProfileAge.jsm");
 ChromeUtils.defineModuleGetter(this, "WindowsRegistry",
                                "resource://gre/modules/WindowsRegistry.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 
 // The maximum length of a string (e.g. description) in the addons section.
@@ -694,28 +692,22 @@ EnvironmentAddonBuilder.prototype = {
    *        situation, we defer some more expensive initialization.
    *
    * @returns Promise<Object> This returns a Promise resolved with a status object with the following members:
    *   changed - Whether the environment changed.
    *   oldEnvironment - Only set if a change occured, contains the environment data before the change.
    */
   async _updateAddons(atStartup) {
     this._environment._log.trace("_updateAddons");
-    let personaId = null;
-    let theme = LightweightThemeManager.currentTheme;
-    if (theme) {
-      personaId = theme.id;
-    }
 
     let addons = {
       activeAddons: await this._getActiveAddons(),
       theme: await this._getActiveTheme(),
       activePlugins: this._getActivePlugins(atStartup),
       activeGMPlugins: await this._getActiveGMPlugins(atStartup),
-      persona: personaId,
     };
 
     let result = {
       changed: !this._environment._currentEnvironment.addons ||
                !ObjectUtils.deepEqual(addons, this._environment._currentEnvironment.addons),
     };
 
     if (result.changed) {
--- a/toolkit/components/telemetry/docs/data/environment.rst
+++ b/toolkit/components/telemetry/docs/data/environment.rst
@@ -270,17 +270,16 @@ Structure:
         activeGMPlugins: {
             <gmp id>: {
                 version: <string>,
                 userDisabled: <bool>,
                 applyBackgroundUpdates: <integer>,
             },
             ...
         },
-        persona: <string>, // id of the current persona
       },
       experiments: {
         "<experiment id>": { branch: "<branch>" },
         // ...
       }
     }
 
 build
@@ -460,12 +459,16 @@ Just like activePlugins, this will repor
 experiments
 -----------
 For each experiment we collect the ``id`` and the ``branch`` the client is enrolled in. Both fields are truncated to 100 characters and a warning is printed when that happens.
 
 
 Version History
 ---------------
 
+- Firefox 67:
+
+  - Removed ``persona``. The ``addons.activeAddons`` list should be used instead. (`bug 1525511 https://bugzilla.mozilla.org/show_bug.cgi?id=1525511>`_)
+
 - Firefox 61:
 
   - Removed empty ``addons.activeExperiment`` (`bug 1452935 <https://bugzilla.mozilla.org/show_bug.cgi?id=1452935>`_).
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -12,20 +12,16 @@ ChromeUtils.import("resource://testing-c
 const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
 const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 // AttributionCode is only needed for Firefox
 ChromeUtils.defineModuleGetter(this, "AttributionCode",
                                "resource:///modules/AttributionCode.jsm");
 
-// Lazy load |LightweightThemeManager|.
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
-                               "resource://gre/modules/LightweightThemeManager.jsm");
-
 ChromeUtils.defineModuleGetter(this, "ExtensionTestUtils",
                                "resource://testing-common/ExtensionXPCShellUtils.jsm");
 
 async function installXPIFromURL(url) {
   let install = await AddonManager.getInstallForURL(url);
   return install.install();
 }
 
@@ -67,22 +63,16 @@ const FLASH_PLUGIN_DESC = "A mock flash 
 const FLASH_PLUGIN_VERSION = "\u201c1.1.1.1\u201d";
 const PLUGIN_MIME_TYPE1 = "application/x-shockwave-flash";
 const PLUGIN_MIME_TYPE2 = "text/plain";
 
 const PLUGIN2_NAME = "Quicktime";
 const PLUGIN2_DESC = "A mock Quicktime plugin";
 const PLUGIN2_VERSION = "2.3";
 
-const PERSONA_ID = "3785";
-// Defined by LightweightThemeManager, it is appended to the PERSONA_ID.
-const PERSONA_ID_SUFFIX = "@personas.mozilla.org";
-const PERSONA_NAME = "Test Theme";
-const PERSONA_DESCRIPTION = "A nice theme/persona description.";
-
 const PLUGIN_UPDATED_TOPIC     = "plugins-list-updated";
 
 // system add-ons are enabled at startup, so record date when the test starts
 const SYSTEM_ADDON_INSTALL_DATE = Date.now();
 
 // Valid attribution code to write so that settings.attribution can be tested.
 const ATTRIBUTION_CODE = "source%3Dgoogle.com";
 
@@ -269,30 +259,16 @@ function createMockAddonProvider(aName) 
     shutdown() {
       return Promise.resolve();
     },
   };
 
   return mockProvider;
 }
 
-/**
- * Used to spoof the Persona Id.
- */
-function spoofTheme(aId, aName, aDesc) {
-  return {
-    id: aId,
-    name: aName,
-    description: aDesc,
-    headerURL: "http://lwttest.invalid/a.png",
-    textcolor: Math.random().toString(),
-    accentcolor: Math.random().toString(),
-  };
-}
-
 function spoofGfxAdapter() {
   try {
     let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfoDebug);
     gfxInfo.fireTestProcess();
     gfxInfo.spoofVendorID(GFX_VENDOR_ID);
     gfxInfo.spoofDeviceID(GFX_DEVICE_ID);
   } catch (x) {
     // If we can't test gfxInfo, that's fine, we'll note it later.
@@ -811,17 +787,16 @@ function checkActiveGMPlugin(data) {
   }
   Assert.equal(typeof data.userDisabled, "boolean");
   Assert.equal(typeof data.applyBackgroundUpdates, "number");
 }
 
 function checkAddonsSection(data, expectBrokenAddons, partialAddonsRecords) {
   const EXPECTED_FIELDS = [
     "activeAddons", "theme", "activePlugins", "activeGMPlugins",
-    "persona",
   ];
 
   Assert.ok("addons" in data, "There must be an addons section in Environment.");
   for (let f of EXPECTED_FIELDS) {
     Assert.ok(f in data.addons, f + " must be available.");
   }
 
   // Check the active addons, if available.
@@ -843,19 +818,16 @@ function checkAddonsSection(data, expect
     checkPlugin(plugin);
   }
 
   // Check active GMPlugins
   let activeGMPlugins = data.addons.activeGMPlugins;
   for (let gmPlugin in activeGMPlugins) {
     checkActiveGMPlugin(activeGMPlugins[gmPlugin]);
   }
-
-  // Check persona
-  Assert.ok(checkNullOrString(data.addons.persona));
 }
 
 function checkExperimentsSection(data) {
   // We don't expect the experiments section to be always available.
   let experiments = data.experiments || {};
   if (Object.keys(experiments).length == 0) {
     return;
   }
@@ -897,20 +869,16 @@ add_task(async function setup() {
   // The system add-on must be installed before AddonManager is started.
   const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true);
   do_get_file("system.xpi").copyTo(distroDir, "tel-system-xpi@tests.mozilla.org.xpi");
   let system_addon = FileUtils.File(distroDir.path);
   system_addon.append("tel-system-xpi@tests.mozilla.org.xpi");
   system_addon.lastModifiedTime = SYSTEM_ADDON_INSTALL_DATE;
   loadAddonManager(APP_ID, APP_NAME, APP_VERSION, PLATFORM_VERSION);
 
-  // Spoof the persona ID.
-  LightweightThemeManager.currentTheme =
-    spoofTheme(PERSONA_ID, PERSONA_NAME, PERSONA_DESCRIPTION);
-
   // The test runs in a fresh profile so starting the AddonManager causes
   // the addons database to be created (as does setting new theme).
   // For test_addonsStartup below, we want to test a "warm" startup where
   // there is already a database on disk.  Simulate that here by just
   // restarting the AddonManager.
   await AddonTestUtils.promiseShutdownManager();
   await AddonTestUtils.overrideBuiltIns({"system": []});
   AddonTestUtils.addonStartup.remove(true);
@@ -1371,36 +1339,28 @@ add_task(async function test_addonsAndPl
   Assert.ok(WEBEXTENSION_ADDON_ID in data.addons.activeAddons, "We must have one active webextension addon.");
   let targetWebExtensionAddon = data.addons.activeAddons[WEBEXTENSION_ADDON_ID];
   for (let f in EXPECTED_WEBEXTENSION_ADDON_DATA) {
     Assert.equal(targetWebExtensionAddon[f], EXPECTED_WEBEXTENSION_ADDON_DATA[f], f + " must have the correct value.");
   }
 
   await webextension.unload();
 
-  // Check theme data.
-  let theme = data.addons.theme;
-  Assert.equal(theme.id, (PERSONA_ID + PERSONA_ID_SUFFIX));
-  Assert.equal(theme.name, PERSONA_NAME);
-  Assert.equal(theme.description, PERSONA_DESCRIPTION);
-
   // Check plugin data.
   Assert.equal(data.addons.activePlugins.length, 1, "We must have only one active plugin.");
   let targetPlugin = data.addons.activePlugins[0];
   for (let f in EXPECTED_PLUGIN_DATA) {
     Assert.equal(targetPlugin[f], EXPECTED_PLUGIN_DATA[f], f + " must have the correct value.");
   }
 
   // Check plugin mime types.
   Assert.ok(targetPlugin.mimeTypes.find(m => m == PLUGIN_MIME_TYPE1));
   Assert.ok(targetPlugin.mimeTypes.find(m => m == PLUGIN_MIME_TYPE2));
   Assert.ok(!targetPlugin.mimeTypes.find(m => m == "Not There."));
 
-  Assert.equal(data.addons.persona, PERSONA_ID, "The correct Persona Id must be reported.");
-
   // Uninstall the addon.
   await addon.startupPromise;
   await addon.uninstall();
 });
 
 add_task(async function test_signedAddon() {
   AddonTestUtils.useRealCertChecks = true;
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -6,17 +6,16 @@
  * Telemetry code keeps histograms of past telemetry pings. The first
  * ping populates these histograms. One of those histograms is then
  * checked in the second request.
  */
 
 const {CommonUtils} = ChromeUtils.import("resource://services-common/utils.js");
 const {ClientID} = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", this);
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetrySession.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryStorage.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetrySend.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
 ChromeUtils.import("resource://gre/modules/TelemetryReportingPolicy.jsm", this);
--- a/toolkit/mozapps/extensions/test/browser/browser_bug562899.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug562899.js
@@ -1,41 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Simulates quickly switching between different list views to verify that only
 // the last selected is displayed
 
-var tempScope = {};
-ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
-var LightweightThemeManager = tempScope.LightweightThemeManager;
+const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
+
+PromiseTestUtils.whitelistRejectionsGlobally(/this\._errorLink/);
 
 var gManagerWindow;
 var gCategoryUtilities;
 
 async function test() {
   waitForExplicitFinish();
 
-  // Add a lightweight theme so at least one theme exists
-  LightweightThemeManager.currentTheme = {
-    id: "test",
-    name: "Test lightweight theme",
-    headerURL: "http://example.com/header.png",
-  };
-
   let aWindow = await open_manager(null);
   gManagerWindow = aWindow;
   gCategoryUtilities = new CategoryUtilities(gManagerWindow);
   run_next_test();
 }
 
 async function end_test() {
   await close_manager(gManagerWindow);
-  LightweightThemeManager.forgetUsedTheme("test");
   finish();
 }
 
 // Tests that loading a second view before the first has not finished loading
 // does not merge the results
 add_test(async function() {
   var themeCount = null;
   var pluginCount = null;
--- a/toolkit/mozapps/extensions/test/browser/browser_bug591465.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug591465.js
@@ -1,34 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Bug 591465 - Context menu of add-ons miss context related state change entries
 
 
-var tempScope = {};
-ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
-var LightweightThemeManager = tempScope.LightweightThemeManager;
-
-
 var gManagerWindow;
 var gProvider;
 var gContextMenu;
-var gLWTheme = {
-                id: "4",
-                version: "1",
-                name: "Bling",
-                description: "SO MUCH BLING!",
-                author: "Pixel Pusher",
-                homepageURL: "http://mochi.test:8888/data/index.html",
-                headerURL: "http://mochi.test:8888/data/header.png",
-                previewURL: "http://mochi.test:8888/data/preview.png",
-                iconURL: "http://mochi.test:8888/data/icon.png",
-              };
 
 
 add_task(async function setup() {
   gProvider = new MockProvider();
 
   gProvider.createAddons([{
     id: "addon1@tests.mozilla.org",
     name: "addon 1",
@@ -217,95 +201,16 @@ add_test(function() {
 
   info("Opening context menu on disabled theme item");
   el.parentNode.ensureElementIsVisible(el);
   EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
   EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
 });
 
 
-add_test(function() {
-  LightweightThemeManager.currentTheme = gLWTheme;
-
-  var el = get_addon_element(gManagerWindow, "4@personas.mozilla.org");
-
-  gContextMenu.addEventListener("popupshown", function() {
-    check_contextmenu(true, true, false, false, false);
-
-    gContextMenu.hidePopup();
-    run_next_test();
-  }, {once: true});
-
-  info("Opening context menu on enabled LW theme item");
-  el.parentNode.ensureElementIsVisible(el);
-  EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
-  EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
-});
-
-
-add_test(function() {
-  LightweightThemeManager.currentTheme = null;
-
-  var el = get_addon_element(gManagerWindow, "4@personas.mozilla.org");
-
-  gContextMenu.addEventListener("popupshown", function() {
-    check_contextmenu(true, false, false, false, false);
-
-    gContextMenu.hidePopup();
-    run_next_test();
-  }, {once: true});
-
-  info("Opening context menu on disabled LW theme item");
-  el.parentNode.ensureElementIsVisible(el);
-  EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
-  EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
-});
-
-
-add_test(async function() {
-  LightweightThemeManager.currentTheme = gLWTheme;
-
-  gManagerWindow.loadView("addons://detail/4@personas.mozilla.org");
-  await wait_for_view_load(gManagerWindow);
-  gContextMenu.addEventListener("popupshown", function() {
-    check_contextmenu(true, true, false, true, false);
-
-    gContextMenu.hidePopup();
-    run_next_test();
-  }, {once: true});
-
-  info("Opening context menu on enabled LW theme, in detail view");
-  var el = gManagerWindow.document.querySelector("#detail-view .detail-view-container");
-  EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
-  EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
-});
-
-
-add_test(async function() {
-  LightweightThemeManager.currentTheme = null;
-
-  gManagerWindow.loadView("addons://detail/4@personas.mozilla.org");
-  await wait_for_view_load(gManagerWindow);
-  gContextMenu.addEventListener("popupshown", async function() {
-    check_contextmenu(true, false, false, true, false);
-
-    gContextMenu.hidePopup();
-
-    let aAddon = await AddonManager.getAddonByID("4@personas.mozilla.org");
-    aAddon.uninstall();
-    run_next_test();
-  }, {once: true});
-
-  info("Opening context menu on disabled LW theme, in detail view");
-  var el = gManagerWindow.document.querySelector("#detail-view .detail-view-container");
-  EventUtils.synthesizeMouse(el, 4, 4, { }, gManagerWindow);
-  EventUtils.synthesizeMouse(el, 4, 4, { type: "contextmenu", button: 2 }, gManagerWindow);
-});
-
-
 add_test(async function() {
   gManagerWindow.loadView("addons://detail/addon1@tests.mozilla.org");
   await wait_for_view_load(gManagerWindow);
   gContextMenu.addEventListener("popupshown", function() {
     check_contextmenu(false, true, false, true, false);
 
     gContextMenu.hidePopup();
     run_next_test();
--- a/toolkit/mozapps/extensions/test/browser/browser_list.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_list.js
@@ -1,42 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests the list view
 
-var tempScope = {};
-ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
-var LightweightThemeManager = tempScope.LightweightThemeManager;
- const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+const {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm");
+
+PromiseTestUtils.whitelistRejectionsGlobally(/this\._errorLink/);
 
 var gProvider;
 var gManagerWindow;
 var gCategoryUtilities;
 
 var gApp = document.getElementById("bundle_brand").getString("brandShortName");
 var gVersion = Services.appinfo.version;
 var gDate = new Date(2010, 7, 16);
 var infoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
 
 const EXPECTED_ADDONS = 11;
 
-var gLWTheme = {
-                id: "4",
-                version: "1",
-                name: "Bling",
-                description: "SO MUCH BLING!",
-                author: "Pixel Pusher",
-                homepageURL: "http://mochi.test:8888/data/index.html",
-                headerURL: "http://mochi.test:8888/data/header.png",
-                previewURL: "http://mochi.test:8888/data/preview.png",
-                iconURL: "http://mochi.test:8888/data/icon.png",
-              };
-
 add_task(async function() {
   gProvider = new MockProvider();
 
   gProvider.createAddons([{
     id: "addon1@tests.mozilla.org",
     name: "Test add-on",
     version: "1.0",
     description: "A test add-on",
@@ -447,48 +436,16 @@ add_task(async function() {
   ok(!is_node_in_list(Services.focus.focusedElement), "Focus should be outside the list");
 
   try {
     Services.prefs.clearUserPref("accessibility.tabfocus_applies_to_xul");
   } catch (e) { }
 });
 
 
-function tick() {
-  return new Promise(SimpleTest.executeSoon);
-}
-
-add_task(async function() {
-  info("Enabling lightweight theme");
-  LightweightThemeManager.currentTheme = gLWTheme;
-  await tick();
-
-  gManagerWindow.loadView("addons://list/theme");
-  await new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
-
-  var addon = get_addon_element(gManagerWindow, "4@personas.mozilla.org");
-
-  is_element_hidden(get_node(addon, "preferences-btn"), "Preferences button should be hidden");
-  is_element_hidden(get_node(addon, "enable-btn"), "Enable button should be hidden");
-  is_element_visible(get_node(addon, "disable-btn"), "Disable button should be visible");
-  is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
-
-  info("Disabling lightweight theme");
-  LightweightThemeManager.currentTheme = null;
-  await tick();
-
-  is_element_hidden(get_node(addon, "preferences-btn"), "Preferences button should be hidden");
-  is_element_visible(get_node(addon, "enable-btn"), "Enable button should be hidden");
-  is_element_hidden(get_node(addon, "disable-btn"), "Disable button should be visible");
-  is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
-
-  let [aAddon] = await promiseAddonsByIDs(["4@personas.mozilla.org"]);
-  aAddon.uninstall();
-});
-
 // Check that onPropertyChanges for appDisabled updates the UI
 add_task(async function() {
   info("Checking that onPropertyChanges for appDisabled updates the UI");
 
   let [aAddon] = await promiseAddonsByIDs(["addon2@tests.mozilla.org"]);
   await aAddon.disable();
   aAddon.isCompatible = true;
   aAddon.appDisabled = false;
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_LightweightThemeManager.js
+++ /dev/null
@@ -1,615 +0,0 @@
-const MANDATORY = ["id", "name"];
-const OPTIONAL = ["headerURL", "textcolor", "accentcolor",
-                  "iconURL", "previewURL", "author", "description",
-                  "homepageURL", "updateURL", "version"];
-
-const DEFAULT_THEME_ID = "default-theme@mozilla.org";
-
-var LightweightThemeManager;
-
-function dummy(id) {
-  return {
-    id: id || Math.random().toString(),
-    name: Math.random().toString(),
-    headerURL: "http://lwttest.invalid/a.png",
-    textcolor: Math.random().toString(),
-    accentcolor: Math.random().toString(),
-  };
-}
-
-function tick() {
-  return new Promise(executeSoon);
-}
-
-function setTheme(theme) {
-  LightweightThemeManager.currentTheme = theme;
-  return tick();
-}
-
-function hasPermission(aAddon, aPerm) {
-  var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
-  return !!(aAddon.permissions & perm);
-}
-
-add_task(async function run_test() {
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
-  await promiseStartupManager();
-
-  Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 8);
-
-  ({LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm"));
-  let ltm = LightweightThemeManager;
-
-  Assert.equal(typeof ltm, "object");
-  Assert.equal(typeof ltm.usedThemes, "object");
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  ltm.previewTheme(dummy("preview0"));
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  ltm.previewTheme(dummy("preview1"));
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-  ltm.resetPreview();
-
-  await setTheme(dummy("x0"));
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.id, "x0");
-  Assert.equal(ltm.usedThemes[0].id, "x0");
-  Assert.equal(ltm.getUsedTheme("x0").id, "x0");
-
-  ltm.previewTheme(dummy("preview0"));
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.id, "x0");
-
-  ltm.resetPreview();
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.id, "x0");
-
-  await setTheme(dummy("x1"));
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.currentTheme.id, "x1");
-  Assert.equal(ltm.usedThemes[1].id, "x0");
-
-  await setTheme(dummy("x2"));
-  Assert.equal(ltm.usedThemes.length, 4);
-  Assert.equal(ltm.currentTheme.id, "x2");
-  Assert.equal(ltm.usedThemes[1].id, "x1");
-  Assert.equal(ltm.usedThemes[2].id, "x0");
-
-  ltm.currentTheme = dummy("x3");
-  ltm.currentTheme = dummy("x4");
-  ltm.currentTheme = dummy("x5");
-  ltm.currentTheme = dummy("x6");
-  ltm.currentTheme = dummy("x7");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 9);
-  Assert.equal(ltm.currentTheme.id, "x7");
-  Assert.equal(ltm.usedThemes[1].id, "x6");
-  Assert.equal(ltm.usedThemes[7].id, "x0");
-
-  await setTheme(dummy("x8"));
-  Assert.equal(ltm.usedThemes.length, 9);
-  Assert.equal(ltm.currentTheme.id, "x8");
-  Assert.equal(ltm.usedThemes[1].id, "x7");
-  Assert.equal(ltm.usedThemes[7].id, "x1");
-  Assert.equal(ltm.getUsedTheme("x0"), null);
-
-  ltm.forgetUsedTheme("nonexistent");
-  Assert.equal(ltm.usedThemes.length, 9);
-  Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  ltm.forgetUsedTheme("x8");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 8);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-  Assert.equal(ltm.usedThemes[0].id, "x7");
-  Assert.equal(ltm.usedThemes[6].id, "x1");
-
-  ltm.forgetUsedTheme("x7");
-  ltm.forgetUsedTheme("x6");
-  ltm.forgetUsedTheme("x5");
-  ltm.forgetUsedTheme("x4");
-  ltm.forgetUsedTheme("x3");
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-  Assert.equal(ltm.usedThemes[0].id, "x2");
-  Assert.equal(ltm.usedThemes[1].id, "x1");
-
-  await setTheme(dummy("x1"));
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.currentTheme.id, "x1");
-  Assert.equal(ltm.usedThemes[0].id, "x1");
-  Assert.equal(ltm.usedThemes[1].id, "x2");
-
-  await setTheme(dummy("x2"));
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.currentTheme.id, "x2");
-  Assert.equal(ltm.usedThemes[0].id, "x2");
-  Assert.equal(ltm.usedThemes[1].id, "x1");
-
-  await setTheme(ltm.getUsedTheme("x1"));
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.currentTheme.id, "x1");
-  Assert.equal(ltm.usedThemes[0].id, "x1");
-  Assert.equal(ltm.usedThemes[1].id, "x2");
-
-  ltm.forgetUsedTheme("x1");
-  ltm.forgetUsedTheme("x2");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  // Use chinese name to test utf-8, for bug #541943
-  var chineseTheme = dummy("chinese0");
-  chineseTheme.name = "笢恅0";
-  chineseTheme.description = "笢恅1";
-  await setTheme(chineseTheme);
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.name, "笢恅0");
-  Assert.equal(ltm.currentTheme.description, "笢恅1");
-  Assert.equal(ltm.usedThemes[0].name, "笢恅0");
-  Assert.equal(ltm.usedThemes[0].description, "笢恅1");
-  Assert.equal(ltm.getUsedTheme("chinese0").name, "笢恅0");
-  Assert.equal(ltm.getUsedTheme("chinese0").description, "笢恅1");
-
-  // This name used to break the usedTheme JSON causing all LWTs to be lost
-  var chineseTheme1 = dummy("chinese1");
-  chineseTheme1.name = "眵昜湮桵蔗坌~郔乾";
-  chineseTheme1.description = "眵昜湮桵蔗坌~郔乾";
-  await setTheme(chineseTheme1);
-  Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.currentTheme.name, "眵昜湮桵蔗坌~郔乾");
-  Assert.equal(ltm.currentTheme.description, "眵昜湮桵蔗坌~郔乾");
-  Assert.equal(ltm.usedThemes[1].name, "笢恅0");
-  Assert.equal(ltm.usedThemes[1].description, "笢恅1");
-  Assert.equal(ltm.usedThemes[0].name, "眵昜湮桵蔗坌~郔乾");
-  Assert.equal(ltm.usedThemes[0].description, "眵昜湮桵蔗坌~郔乾");
-
-  ltm.forgetUsedTheme("chinese0");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  ltm.forgetUsedTheme("chinese1");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  Assert.equal(ltm.parseTheme("invalid json"), null);
-  Assert.equal(ltm.parseTheme('"json string"'), null);
-
-  function roundtrip(data, secure) {
-    return ltm.parseTheme(JSON.stringify(data),
-                          "http" + (secure ? "s" : "") + "://lwttest.invalid/");
-  }
-
-  var data = dummy();
-  Assert.notEqual(roundtrip(data), null);
-  data.id = null;
-  Assert.equal(roundtrip(data), null);
-  data.id = 1;
-  Assert.equal(roundtrip(data), null);
-  data.id = 1.5;
-  Assert.equal(roundtrip(data), null);
-  data.id = true;
-  Assert.equal(roundtrip(data), null);
-  data.id = {};
-  Assert.equal(roundtrip(data), null);
-  data.id = [];
-  Assert.equal(roundtrip(data), null);
-
-  // Check whether parseTheme handles international characters right
-  var chineseTheme2 = dummy();
-  chineseTheme2.name = "眵昜湮桵蔗坌~郔乾";
-  chineseTheme2.description = "眵昜湮桵蔗坌~郔乾";
-  Assert.notEqual(roundtrip(chineseTheme2), null);
-  Assert.equal(roundtrip(chineseTheme2).name, "眵昜湮桵蔗坌~郔乾");
-  Assert.equal(roundtrip(chineseTheme2).description, "眵昜湮桵蔗坌~郔乾");
-
-  data = dummy();
-  data.unknownProperty = "Foo";
-  Assert.equal(typeof roundtrip(data).unknownProperty, "undefined");
-
-  data = dummy();
-  data.unknownURL = "http://lwttest.invalid/";
-  Assert.equal(typeof roundtrip(data).unknownURL, "undefined");
-
-  function roundtripSet(props, modify, test, secure) {
-    props.forEach(function(prop) {
-      var theme = dummy();
-      modify(theme, prop);
-      test(roundtrip(theme, secure), prop, theme);
-    });
-  }
-
-  roundtripSet(MANDATORY, function(theme, prop) {
-    delete theme[prop];
-  }, function(after) {
-    Assert.equal(after, null);
-  });
-
-  roundtripSet(OPTIONAL, function(theme, prop) {
-    delete theme[prop];
-  }, function(after) {
-    Assert.notEqual(after, null);
-  });
-
-  roundtripSet(MANDATORY, function(theme, prop) {
-    theme[prop] = "";
-  }, function(after) {
-    Assert.equal(after, null);
-  });
-
-  roundtripSet(OPTIONAL, function(theme, prop) {
-    theme[prop] = "";
-  }, function(after, prop) {
-    Assert.equal(typeof after[prop], "undefined");
-  });
-
-  roundtripSet(MANDATORY, function(theme, prop) {
-    theme[prop] = " ";
-  }, function(after) {
-    Assert.equal(after, null);
-  });
-
-  roundtripSet(OPTIONAL, function(theme, prop) {
-    theme[prop] = " ";
-  }, function(after, prop) {
-    Assert.notEqual(after, null);
-    Assert.equal(typeof after[prop], "undefined");
-  });
-
-  function non_urls(props) {
-    return props.filter(prop => !/URL$/.test(prop));
-  }
-
-  function urls(props) {
-    return props.filter(prop => /URL$/.test(prop));
-  }
-
-  roundtripSet(non_urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
-    theme[prop] = prop;
-  }, function(after, prop, before) {
-    Assert.equal(after[prop], before[prop]);
-  });
-
-  roundtripSet(non_urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
-    theme[prop] = " " + prop + "  ";
-  }, function(after, prop, before) {
-    Assert.equal(after[prop], before[prop].trim());
-  });
-
-  roundtripSet(urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
-    theme[prop] = Math.random().toString();
-  }, function(after, prop, before) {
-    if (prop == "updateURL")
-      Assert.equal(typeof after[prop], "undefined");
-    else
-      Assert.equal(after[prop], "http://lwttest.invalid/" + before[prop]);
-  });
-
-  roundtripSet(urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
-    theme[prop] = Math.random().toString();
-  }, function(after, prop, before) {
-    Assert.equal(after[prop], "https://lwttest.invalid/" + before[prop]);
-  }, true);
-
-  roundtripSet(urls(MANDATORY.concat(OPTIONAL)), function(theme, prop) {
-    theme[prop] = "https://sub.lwttest.invalid/" + Math.random().toString();
-  }, function(after, prop, before) {
-    Assert.equal(after[prop], before[prop]);
-  });
-
-  roundtripSet(urls(MANDATORY), function(theme, prop) {
-    theme[prop] = "ftp://lwttest.invalid/" + Math.random().toString();
-  }, function(after) {
-    Assert.equal(after, null);
-  });
-
-  roundtripSet(urls(OPTIONAL), function(theme, prop) {
-    theme[prop] = "ftp://lwttest.invalid/" + Math.random().toString();
-  }, function(after, prop) {
-    Assert.equal(typeof after[prop], "undefined");
-  });
-
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  data = dummy();
-  delete data.name;
-  try {
-    ltm.currentTheme = data;
-    do_throw("Should have rejected a theme with no name");
-  } catch (e) {
-    // Expected exception
-  }
-
-  // Sanitize themes with a bad headerURL
-  data = dummy();
-  data.headerURL = "foo";
-  await setTheme(data);
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.headerURL, undefined);
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  // Sanitize themes with a non-http(s) headerURL
-  data = dummy();
-  data.headerURL = "ftp://lwtest.invalid/test.png";
-  await setTheme(data);
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.headerURL, undefined);
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  // Sanitize themes with a non-http(s) headerURL
-  data = dummy();
-  data.headerURL = "file:///test.png";
-  await setTheme(data);
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.headerURL, undefined);
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  data = dummy();
-  data.updateURL = "file:///test.json";
-  ltm.setLocalTheme(data);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.updateURL, undefined);
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  data = dummy();
-  data.headerURL = "file:///test.png";
-  ltm.setLocalTheme(data);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.headerURL, "file:///test.png");
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  data = dummy();
-  data.headerURL = "ftp://lwtest.invalid/test.png";
-  ltm.setLocalTheme(data);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.currentTheme.updateURL, undefined);
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  data = dummy();
-  delete data.id;
-  try {
-    ltm.currentTheme = data;
-    do_throw("Should have rejected a theme with no ID");
-  } catch (e) {
-    // Expected exception
-  }
-
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  // Force the theme into the prefs anyway
-  let themes = [data];
-  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
-  Assert.equal(ltm.usedThemes.length, 2);
-
-  // This should silently drop the bad theme.
-  await setTheme(dummy());
-  Assert.equal(ltm.usedThemes.length, 2);
-  ltm.forgetUsedTheme(ltm.currentTheme.id);
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  // Add one broken and some working.
-  themes = [data, dummy("x1"), dummy("x2")];
-  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
-  Assert.equal(ltm.usedThemes.length, 4);
-
-  // Switching to an existing theme should drop the bad theme.
-  await setTheme(ltm.getUsedTheme("x1"));
-  Assert.equal(ltm.usedThemes.length, 3);
-  ltm.forgetUsedTheme("x1");
-  ltm.forgetUsedTheme("x2");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
-  Assert.equal(ltm.usedThemes.length, 4);
-
-  // Forgetting an existing theme should drop the bad theme.
-  ltm.forgetUsedTheme("x1");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 2);
-  ltm.forgetUsedTheme("x2");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  // Test whether a JSON set with setCharPref can be retrieved with usedThemes
-  ltm.currentTheme = dummy("x0");
-  ltm.currentTheme = dummy("x1");
-  await tick();
-  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(ltm.usedThemes));
-  Assert.equal(ltm.usedThemes.length, 4);
-  Assert.equal(ltm.currentTheme.id, "x1");
-  Assert.equal(ltm.usedThemes[1].id, "x0");
-  Assert.equal(ltm.usedThemes[0].id, "x1");
-
-  ltm.forgetUsedTheme("x0");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.notEqual(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  ltm.forgetUsedTheme("x1");
-  await tick();
-  Assert.equal(ltm.usedThemes.length, 1);
-  Assert.equal(ltm.currentTheme.id, DEFAULT_THEME_ID);
-
-  Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes");
-
-  ltm.currentTheme = dummy("x1");
-  ltm.currentTheme = dummy("x2");
-  ltm.currentTheme = dummy("x3");
-  ltm.currentTheme = dummy("x4");
-  ltm.currentTheme = dummy("x5");
-  ltm.currentTheme = dummy("x6");
-  ltm.currentTheme = dummy("x7");
-  ltm.currentTheme = dummy("x8");
-  ltm.currentTheme = dummy("x9");
-  ltm.currentTheme = dummy("x10");
-  ltm.currentTheme = dummy("x11");
-  ltm.currentTheme = dummy("x12");
-  ltm.currentTheme = dummy("x13");
-  ltm.currentTheme = dummy("x14");
-  ltm.currentTheme = dummy("x15");
-  ltm.currentTheme = dummy("x16");
-  ltm.currentTheme = dummy("x17");
-  ltm.currentTheme = dummy("x18");
-  ltm.currentTheme = dummy("x19");
-  ltm.currentTheme = dummy("x20");
-  ltm.currentTheme = dummy("x21");
-  ltm.currentTheme = dummy("x22");
-  ltm.currentTheme = dummy("x23");
-  ltm.currentTheme = dummy("x24");
-  ltm.currentTheme = dummy("x25");
-  ltm.currentTheme = dummy("x26");
-  ltm.currentTheme = dummy("x27");
-  ltm.currentTheme = dummy("x28");
-  ltm.currentTheme = dummy("x29");
-  ltm.currentTheme = dummy("x30");
-  await tick();
-
-  Assert.equal(ltm.usedThemes.length, 31);
-
-  ltm.currentTheme = dummy("x31");
-  await tick();
-
-  Assert.equal(ltm.usedThemes.length, 31);
-  Assert.equal(ltm.getUsedTheme("x1"), null);
-
-  Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 15);
-
-  Assert.equal(ltm.usedThemes.length, 16);
-
-  Services.prefs.setIntPref("lightweightThemes.maxUsedThemes", 32);
-
-  ltm.currentTheme = dummy("x1");
-  ltm.currentTheme = dummy("x2");
-  ltm.currentTheme = dummy("x3");
-  ltm.currentTheme = dummy("x4");
-  ltm.currentTheme = dummy("x5");
-  ltm.currentTheme = dummy("x6");
-  ltm.currentTheme = dummy("x7");
-  ltm.currentTheme = dummy("x8");
-  ltm.currentTheme = dummy("x9");
-  ltm.currentTheme = dummy("x10");
-  ltm.currentTheme = dummy("x11");
-  ltm.currentTheme = dummy("x12");
-  ltm.currentTheme = dummy("x13");
-  ltm.currentTheme = dummy("x14");
-  ltm.currentTheme = dummy("x15");
-  ltm.currentTheme = dummy("x16");
-
-  ltm.currentTheme = dummy("x32");
-  await tick();
-
-  Assert.equal(ltm.usedThemes.length, 33);
-
-  ltm.currentTheme = dummy("x33");
-  await tick();
-
-  Assert.equal(ltm.usedThemes.length, 33);
-
-  Services.prefs.clearUserPref("lightweightThemes.maxUsedThemes");
-
-  Assert.equal(ltm.usedThemes.length, 31);
-
-  let usedThemes = ltm.usedThemes;
-  for (let theme of usedThemes) {
-    ltm.forgetUsedTheme(theme.id);
-  }
-
-  // Check builtInTheme functionality for Bug 1094821
-  Assert.equal(ltm._builtInThemes.toString(), "[object Map]");
-  Assert.equal([...ltm._builtInThemes.entries()].length, 1);
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  ltm.addBuiltInTheme(dummy("builtInTheme0"));
-  Assert.equal([...ltm._builtInThemes].length, 2);
-  Assert.equal(ltm.usedThemes.length, 2);
-  Assert.equal(ltm.usedThemes[1].id, "builtInTheme0");
-
-  ltm.addBuiltInTheme(dummy("builtInTheme1"));
-  Assert.equal([...ltm._builtInThemes].length, 3);
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.usedThemes[2].id, "builtInTheme1");
-
-  // Clear all and then re-add
-  ltm.clearBuiltInThemes();
-  Assert.equal([...ltm._builtInThemes].length, 0);
-  Assert.equal(ltm.usedThemes.length, 0);
-
-  ltm.addBuiltInTheme(dummy("builtInTheme0"));
-  ltm.addBuiltInTheme(dummy("builtInTheme1"));
-  Assert.equal([...ltm._builtInThemes].length, 2);
-  Assert.equal(ltm.usedThemes.length, 2);
-
-  let builtInThemeAddon = await AddonManager.getAddonByID("builtInTheme0@personas.mozilla.org");
-  Assert.equal(hasPermission(builtInThemeAddon, "uninstall"), false);
-  Assert.equal(hasPermission(builtInThemeAddon, "disable"), false);
-  Assert.equal(hasPermission(builtInThemeAddon, "enable"), true);
-
-  await setTheme(dummy("x0"));
-  Assert.equal([...ltm._builtInThemes].length, 2);
-  Assert.equal(ltm.usedThemes.length, 3);
-  Assert.equal(ltm.usedThemes[0].id, "x0");
-  Assert.equal(ltm.currentTheme.id, "x0");
-  Assert.equal(ltm.usedThemes[1].id, "builtInTheme0");
-  Assert.equal(ltm.usedThemes[2].id, "builtInTheme1");
-
-  Assert.throws(() => { ltm.addBuiltInTheme(dummy("builtInTheme0")); },
-    /Error: Trying to add invalid builtIn theme/,
-    "Exception is thrown adding a duplicate theme");
-  Assert.throws(() => { ltm.addBuiltInTheme("not a theme object"); },
-    /Error: Trying to add invalid builtIn theme/,
-    "Exception is thrown adding an invalid theme");
-
-  let x0Addon = await AddonManager.getAddonByID("x0@personas.mozilla.org");
-  Assert.equal(hasPermission(x0Addon, "uninstall"), true);
-  Assert.equal(hasPermission(x0Addon, "disable"), true);
-  Assert.equal(hasPermission(x0Addon, "enable"), false);
-
-  ltm.forgetUsedTheme("x0");
-  await tick();
-  Assert.equal(ltm.currentTheme, null);
-
-  // Removing the currently applied app specific theme should unapply it
-  await setTheme(ltm.getUsedTheme("builtInTheme0"));
-  Assert.equal(ltm.currentTheme.id, "builtInTheme0");
-  Assert.ok(ltm.forgetBuiltInTheme("builtInTheme0"));
-  Assert.equal(ltm.currentTheme, null);
-
-  Assert.equal([...ltm._builtInThemes].length, 1);
-  Assert.equal(ltm.usedThemes.length, 1);
-
-  Assert.ok(ltm.forgetBuiltInTheme("builtInTheme1"));
-  Assert.ok(!ltm.forgetBuiltInTheme("not-an-existing-theme-id"));
-
-  Assert.equal([...ltm._builtInThemes].length, 0);
-  Assert.equal(ltm.usedThemes.length, 0);
-  Assert.equal(ltm.currentTheme, null);
-});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_theme_update.js
+++ /dev/null
@@ -1,182 +0,0 @@
-"use strict";
-
-const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
-
-Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
-
-add_task(async function test_theme_updates() {
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
-
-  const server = createHttpServer({hosts: ["example.com"]});
-
-  let updates = {};
-  server.registerPrefixHandler("/", (request, response) => {
-    let update = updates[request.path];
-    if (update) {
-      response.setHeader("content-type", "application/json");
-      response.write(JSON.stringify(update));
-    } else {
-      response.setStatusLine("1.1", 404, "Not Found");
-    }
-  });
-
-  // Themes 1 and 2 will be updated to xpi themes, theme 3 will
-  // (attempt to) be updated to a regular webextension.
-  let theme1XPI = createTempWebExtensionFile({
-    manifest: {
-      name: "Theme 1",
-      version: "2.0",
-      theme: {
-        images: { headerURL: "webextension.png" },
-      },
-    },
-  });
-  server.registerFile("/theme1.xpi", theme1XPI);
-
-  let theme2XPI = createTempWebExtensionFile({
-    manifest: {
-      name: "Theme 2",
-      version: "2.0",
-      theme: {
-        images: { headerURL: "webextension.png" },
-      },
-    },
-  });
-  server.registerFile("/theme2.xpi", theme2XPI);
-
-  let theme3XPI = createTempWebExtensionFile({
-    manifest: {
-      name: "Theme 3",
-      version: "2.0",
-    },
-  });
-  server.registerFile("/theme3.xpi", theme3XPI);
-
-  await promiseStartupManager();
-
-  function makeTheme(n, {image = "orig.png", version = "1.0"} = {}) {
-    return {
-      id: `theme${n}`,
-      name: `Theme ${n}`,
-      version,
-      updateURL: `http://example.com/${n}`,
-      headerURL: `http://example.com/${image}`,
-      textcolor: "#000000",
-      accentcolor: "#ffffff",
-    };
-  }
-
-  // "Install" some LWTs.  Leave theme 2 as current
-  LightweightThemeManager.currentTheme = makeTheme(1);
-  LightweightThemeManager.currentTheme = makeTheme(3);
-  LightweightThemeManager.currentTheme = makeTheme(2);
-
-  function filterThemes(themes) {
-    return themes.filter(t => t.id != "default-theme@mozilla.org");
-  }
-
-  equal(filterThemes(LightweightThemeManager.usedThemes).length, 3,
-        "Have 3 lightweight themes");
-
-  // Update URLs are all 404, nothing should change
-  await AddonManagerPrivate.backgroundUpdateCheck();
-
-  equal(filterThemes(LightweightThemeManager.usedThemes).length, 3,
-        "Have 3 lightweight themes");
-
-  // Make updates to all LWTs available, only the active on should be updated
-  updates["/1"] = makeTheme(1, {image: "new.png", version: "1.1"});
-  updates["/2"] = makeTheme(2, {image: "new.png", version: "1.1"});
-  updates["/3"] = makeTheme(3, {image: "new.png", version: "1.1"});
-  await AddonManagerPrivate.backgroundUpdateCheck();
-
-  equal(LightweightThemeManager.currentTheme.id, "theme2",
-        "Theme 2 is currently selected");
-
-  let lwThemes = filterThemes(LightweightThemeManager.usedThemes);
-  let theme1 = lwThemes.find(t => t.id == "theme1");
-  notEqual(theme1, undefined, "Found theme1");
-  ok(theme1.headerURL.endsWith("orig.png"), "LWT 1 was not updated");
-
-  let theme2 = lwThemes.find(t => t.id == "theme2");
-  notEqual(theme2, undefined, "Found theme2");
-  dump(`theme2 ${JSON.stringify(theme2)}\n`);
-  ok(theme2.headerURL.endsWith("new.png"), "LWT 2 was updated");
-
-  let theme3 = lwThemes.find(t => t.id == "theme3");
-  notEqual(theme3, undefined, "Found theme3");
-  ok(theme3.headerURL.endsWith("orig.png"), "LWT 3 was not updated");
-
-  // Update an inactive LWT to an XPI packaged theme
-  updates["/1"] = {
-    converted_theme: {
-      url: "http://example.com/theme1.xpi",
-      hash: do_get_file_hash(theme1XPI),
-    },
-  };
-
-  await AddonManagerPrivate.backgroundUpdateCheck();
-
-  let themes = filterThemes(await AddonManager.getAddonsByTypes(["theme"]));
-  equal(themes.length, 3, "Still have a total of 3 themes");
-  equal(filterThemes(LightweightThemeManager.usedThemes).length, 2,
-        "Have 2 lightweight themes");
-  theme1 = themes.find(t => t.name == "Theme 1");
-  notEqual(theme1, undefined, "Found theme1");
-  ok(!theme1.id.endsWith("@personas.mozilla.org"),
-     "Theme 1 has been updated to an XPI packaged theme");
-  equal(theme1.userDisabled, true, "Theme 1 is not active");
-
-  Assert.deepEqual(theme1.installTelemetryInfo, {
-    source: "lwt-converted-theme",
-  }, "Got the expected source on the converted webextension theme");
-
-  // Update the current LWT to an XPI
-  updates["/2"] = {
-    converted_theme: {
-      url: "http://example.com/theme2.xpi",
-      hash: do_get_file_hash(theme2XPI),
-    },
-  };
-
-  await AddonManagerPrivate.backgroundUpdateCheck();
-  themes = filterThemes(await AddonManager.getAddonsByTypes(["theme"]));
-  equal(themes.length, 3, "Still have a total of 3 themes");
-  equal(filterThemes(LightweightThemeManager.usedThemes).length, 1,
-        "Have 1 lightweight theme");
-  theme2 = themes.find(t => t.name == "Theme 2");
-  notEqual(theme2, undefined, "Found theme2");
-  ok(!theme2.id.endsWith("@personas.mozilla.org"),
-     "Theme 2 has been updated to an XPI packaged theme");
-  equal(theme2.userDisabled, false, "Theme 2 is active");
-
-  Assert.deepEqual(theme2.installTelemetryInfo, {
-    source: "lwt-converted-theme",
-  }, "Got the expected source on the converted webextension theme");
-
-  // Serve an update with a bad hash, the LWT should remain in place.
-  updates["/3"] = {
-    converted_theme: {
-      url: "http://example.com/theme3.xpi",
-      hash: "sha256:abcd",
-    },
-  };
-
-  await AddonManagerPrivate.backgroundUpdateCheck();
-  equal(filterThemes(LightweightThemeManager.usedThemes).length, 1,
-        "Still have 1 lightweight theme");
-
-  // Try updating to a regular (non-theme) webextension, the
-  // LWT should remain in place.
-  updates["/3"] = {
-    converted_theme: {
-      url: "http://example.com/theme3.xpi",
-      hash: do_get_file_hash(theme3XPI),
-    },
-  };
-  await AddonManagerPrivate.backgroundUpdateCheck();
-  equal(filterThemes(LightweightThemeManager.usedThemes).length, 1,
-        "Still have 1 lightweight theme");
-
-  await promiseShutdownManager();
-});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -6,20 +6,16 @@
 
 // The test extension uses an insecure update url.
 Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
 
 // This test requires lightweight themes update to be enabled even if the app
 // doesn't support lightweight themes.
 Services.prefs.setBoolPref("lightweightThemes.update.enabled", true);
 
-const {LightweightThemeManager} = ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm");
-
-var gInstallDate;
-
 const updateFile = "test_update.json";
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 const ADDONS = {
   test_update: {
     id: "addon1@tests.mozilla.org",
@@ -766,163 +762,16 @@ add_task(async function test_no_auto_upd
   equal(a1.version, "2.0");
   await a1.uninstall();
 
   notEqual(a8, null);
   equal(a8.version, "1.0");
   await a8.uninstall();
 });
 
-// Test that background update checks work for lightweight themes
-add_task(async function test_lwt_update() {
-  LightweightThemeManager.currentTheme = {
-    id: "1",
-    version: "1",
-    name: "Test LW Theme",
-    description: "A test theme",
-    author: "Mozilla",
-    homepageURL: "http://example.com/data/index.html",
-    headerURL: "http://example.com/data/header.png",
-    previewURL: "http://example.com/data/preview.png",
-    iconURL: "http://example.com/data/icon.png",
-    updateURL: "http://example.com/data/lwtheme.js",
-  };
-
-  // XXX The lightweight theme manager strips non-https updateURLs so hack it
-  // back in.
-  let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes"));
-  equal(themes.length, 1);
-  themes[0].updateURL = "http://example.com/data/lwtheme.js";
-  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
-
-  testserver.registerPathHandler("/data/lwtheme.js", function(request, response) {
-    // Server will specify an expiry in one year.
-    let expiry = new Date();
-    expiry.setFullYear(expiry.getFullYear() + 1);
-    response.setHeader("Expires", expiry.toUTCString(), false);
-    response.write(JSON.stringify({
-      id: "1",
-      version: "2",
-      name: "Updated Theme",
-      description: "A test theme",
-      author: "Mozilla",
-      homepageURL: "http://example.com/data/index2.html",
-      headerURL: "http://example.com/data/header.png",
-      previewURL: "http://example.com/data/preview.png",
-      iconURL: "http://example.com/data/icon2.png",
-      updateURL: "http://example.com/data/lwtheme.js",
-    }));
-  });
-
-  let p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
-  notEqual(p1, null);
-  equal(p1.version, "1");
-  equal(p1.name, "Test LW Theme");
-  ok(p1.isActive);
-  equal(p1.installDate.getTime(), p1.updateDate.getTime());
-
-  // 5 seconds leeway seems like a lot, but tests can run slow and really if
-  // this is within 5 seconds it is fine. If it is going to be wrong then it
-  // is likely to be hours out at least
-  ok((Date.now() - p1.installDate.getTime()) < 5000);
-
-  gInstallDate = p1.installDate.getTime();
-
-  await new Promise(resolve => {
-    prepare_test({
-      "1@personas.mozilla.org": [
-        ["onInstalling", false],
-        "onInstalled",
-      ],
-    }, [
-      "onExternalInstall",
-    ], resolve);
-
-    AddonManagerInternal.backgroundUpdateCheck();
-  });
-
-  p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
-  notEqual(p1, null);
-  equal(p1.version, "2");
-  equal(p1.name, "Updated Theme");
-  equal(p1.installDate.getTime(), gInstallDate);
-  ok(p1.installDate.getTime() < p1.updateDate.getTime());
-
-  // 5 seconds leeway seems like a lot, but tests can run slow and really if
-  // this is within 5 seconds it is fine. If it is going to be wrong then it
-  // is likely to be hours out at least
-  ok((Date.now() - p1.updateDate.getTime()) < 5000);
-
-  gInstallDate = p1.installDate.getTime();
-});
-
-// Test that background update checks for lightweight themes do not use the cache
-// The update body from test 7 shouldn't be used since the cache should be bypassed.
-add_task(async function() {
-  // XXX The lightweight theme manager strips non-https updateURLs so hack it
-  // back in.
-  let themes = JSON.parse(Services.prefs.getCharPref("lightweightThemes.usedThemes"));
-  equal(themes.length, 1);
-  themes[0].updateURL = "http://example.com/data/lwtheme.js";
-  Services.prefs.setCharPref("lightweightThemes.usedThemes", JSON.stringify(themes));
-
-  testserver.registerPathHandler("/data/lwtheme.js", function(request, response) {
-    response.write(JSON.stringify({
-      id: "1",
-      version: "3",
-      name: "Updated Theme v.3",
-      description: "A test theme v.3",
-      author: "John Smith",
-      homepageURL: "http://example.com/data/index3.html?v=3",
-      headerURL: "http://example.com/data/header.png?v=3",
-      previewURL: "http://example.com/data/preview.png?v=3",
-      iconURL: "http://example.com/data/icon2.png?v=3",
-      updateURL: "https://example.com/data/lwtheme.js?v=3",
-    }));
-  });
-
-  let p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
-  notEqual(p1, null);
-  equal(p1.version, "2");
-  equal(p1.name, "Updated Theme");
-  ok(p1.isActive);
-  equal(p1.installDate.getTime(), gInstallDate);
-  ok(p1.installDate.getTime() < p1.updateDate.getTime());
-
-  await new Promise(resolve => {
-    prepare_test({
-      "1@personas.mozilla.org": [
-        ["onInstalling", false],
-        "onInstalled",
-      ],
-    }, [
-      "onExternalInstall",
-    ], resolve);
-
-    AddonManagerInternal.backgroundUpdateCheck();
-  });
-
-  p1 = await AddonManager.getAddonByID("1@personas.mozilla.org");
-  let currentTheme = LightweightThemeManager.currentTheme;
-  notEqual(p1, null);
-  equal(p1.version, "3");
-  equal(p1.name, "Updated Theme v.3");
-  equal(p1.description, "A test theme v.3");
-  info(JSON.stringify(p1));
-  equal(p1.creator.name, "John Smith");
-  equal(p1.homepageURL, "http://example.com/data/index3.html?v=3");
-  equal(p1.screenshots[0].url, "http://example.com/data/preview.png?v=3");
-  equal(p1.iconURL, "http://example.com/data/icon2.png?v=3");
-  equal(currentTheme.headerURL, "http://example.com/data/header.png?v=3");
-  equal(currentTheme.updateURL, "https://example.com/data/lwtheme.js?v=3");
-
-  equal(p1.installDate.getTime(), gInstallDate);
-  ok(p1.installDate.getTime() < p1.updateDate.getTime());
-});
-
 // Test that the update check returns nothing for addons in locked install
 // locations.
 add_task(async function run_test_locked_install() {
   const lockedDir = gProfD.clone();
   lockedDir.append("locked_extensions");
   registerDirectory("XREAppFeat", lockedDir);
 
   await promiseShutdownManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_theme.js
@@ -3,23 +3,22 @@
 /**
  * This file contains test for 'theme' type WebExtension addons. Tests focus mostly
  * on interoperability between the different theme formats (XUL and LWT) and
  * Addon Manager integration.
  *
  * Coverage may overlap with other tests in this folder.
  */
 
-ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
-                               "resource://gre/modules/LightweightThemeManager.jsm");
 const THEME_IDS = [
   "theme3@tests.mozilla.org",
-  "theme2@personas.mozilla.org",
+  "theme2@personas.mozilla.org", // Unused. Legacy. Evil.
   "default-theme@mozilla.org",
 ];
+const REAL_THEME_IDS = [THEME_IDS[0], THEME_IDS[2]];
 const DEFAULT_THEME = THEME_IDS[2];
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 // We remember the last/ currently active theme for tracking events.
 var gActiveTheme = null;
 
@@ -36,48 +35,30 @@ add_task(async function setup_to_default
       gecko: {
         id: THEME_IDS[0],
       },
     },
   }, profileDir);
 
   await promiseStartupManager();
 
-  // We can add an LWT only after the Addon Manager was started.
-  LightweightThemeManager.currentTheme = {
-    id: THEME_IDS[1].substr(0, THEME_IDS[1].indexOf("@")),
-    version: "1",
-    name: "Bling",
-    description: "SO MUCH BLING!",
-    author: "Pixel Pusher",
-    homepageURL: "http://localhost:8888/data/index.html",
-    headerURL: "http://localhost:8888/data/header.png",
-    previewURL: "http://localhost:8888/data/preview.png",
-    iconURL: "http://localhost:8888/data/icon.png",
-    textcolor: Math.random().toString(),
-    accentcolor: Math.random().toString(),
-  };
-
   let [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS);
   Assert.ok(t1, "Theme addon should exist");
-  Assert.ok(t2, "Theme addon should exist");
+  Assert.equal(t2, null, "Theme addon is not a thing anymore");
   Assert.ok(d, "Theme addon should exist");
 
   await t1.disable();
-  await t2.disable();
   await new Promise(executeSoon);
   Assert.ok(!t1.isActive, "Theme should be disabled");
-  Assert.ok(!t2.isActive, "Theme should be disabled");
   Assert.ok(d.isActive, "Default theme should be active");
 
   await promiseRestartManager();
 
   [ t1, t2, d ] = await promiseAddonsByIDs(THEME_IDS);
   Assert.ok(!t1.isActive, "Theme should still be disabled");
-  Assert.ok(!t2.isActive, "Theme should still be disabled");
   Assert.ok(d.isActive, "Default theme should still be active");
 
   gActiveTheme = d.id;
 });
 
 /**
  * Set the `userDisabled` property of one specific theme and check if the theme
  * switching works as expected by checking the state of all installed themes.
@@ -112,30 +93,30 @@ async function setDisabledStateAndCheck(
   prepare_test(expectedEvents);
   if (disabled) {
     await theme.disable();
   } else {
     await theme.enable();
   }
 
   let isDisabled;
-  for (theme of await promiseAddonsByIDs(THEME_IDS)) {
+  for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
     isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
     Assert.equal(theme.userDisabled, isDisabled,
       `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
     Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE,
       "There should be no pending operations when no restart is expected");
     Assert.equal(theme.isActive, !isDisabled,
       `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`);
   }
 
   await promiseRestartManager();
 
   // All should still be good after a restart of the Addon Manager.
-  for (theme of await promiseAddonsByIDs(THEME_IDS)) {
+  for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
     isDisabled = (theme.id in expectedStates) ? expectedStates[theme.id] : true;
     Assert.equal(theme.userDisabled, isDisabled,
       `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`);
     Assert.equal(theme.isActive, !isDisabled,
       `Theme '${theme.id}' should be ${isDisabled ? "in" : ""}active`);
     Assert.equal(theme.pendingOperations, AddonManager.PENDING_NONE,
       "There should be no pending operations left");
     if (!isDisabled)
@@ -149,39 +130,16 @@ add_task(async function test_WebExtensio
   // Enable the WebExtension theme.
   await setDisabledStateAndCheck(THEME_IDS[0]);
 
   // Disabling WebExtension should revert to the default theme.
   await setDisabledStateAndCheck(THEME_IDS[0], true);
 
   // Enable it again.
   await setDisabledStateAndCheck(THEME_IDS[0]);
-
-  // Enabling an LWT should disable the active theme.
-  await setDisabledStateAndCheck(THEME_IDS[1]);
-
-  // Switching back should disable the LWT.
-  await setDisabledStateAndCheck(THEME_IDS[0]);
-});
-
-add_task(async function test_LWTs() {
-  // Start with enabling an LWT.
-  await setDisabledStateAndCheck(THEME_IDS[1]);
-
-  // Disabling LWT should revert to the default theme.
-  await setDisabledStateAndCheck(THEME_IDS[1], true);
-
-  // Enable it again.
-  await setDisabledStateAndCheck(THEME_IDS[1]);
-
-  // Enabling a WebExtension theme should disable the active theme.
-  await setDisabledStateAndCheck(THEME_IDS[0]);
-
-  // Switching back should disable the LWT.
-  await setDisabledStateAndCheck(THEME_IDS[1]);
 });
 
 add_task(async function test_default_theme() {
   // Explicitly enable the default theme.
   await setDisabledStateAndCheck(DEFAULT_THEME);
 
   // Swith to the WebExtension theme.
   await setDisabledStateAndCheck(THEME_IDS[0]);
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -9,17 +9,16 @@ support-files =
 
 [test_addon_manager_telemetry_events.js]
 [test_AddonRepository.js]
 [test_AddonRepository_cache.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_AddonRepository_langpacks.js]
 [test_AddonRepository_paging.js]
-[test_LightweightThemeManager.js]
 [test_ProductAddonChecker.js]
 [test_XPIStates.js]
 [test_XPIcancel.js]
 [test_addonStartup.js]
 [test_bad_json.js]
 [test_badschema.js]
 [test_blocklist_appversion.js]
 # Bug 676992: test consistently hangs on Android
@@ -224,17 +223,16 @@ head = head_addons.js head_system_addons
 head = head_addons.js head_system_addons.js
 [test_system_update_uninstall_check.js]
 head = head_addons.js head_system_addons.js
 [test_system_update_upgrades.js]
 head = head_addons.js head_system_addons.js
 [test_temporary.js]
 skip-if = os == "win" # Bug 1469904
 tags = webextensions
-[test_theme_update.js]
 [test_trash_directory.js]
 skip-if = os != "win"
 [test_types.js]
 [test_systemaddomstartupprefs.js]
 head = head_addons.js head_system_addons.js
 [test_undouninstall.js]
 skip-if = os == "win" # Bug 1358846
 [test_update.js]
--- a/toolkit/profile/nsIToolkitProfile.idl
+++ b/toolkit/profile/nsIToolkitProfile.idl
@@ -41,17 +41,17 @@ interface nsIProfileLock : nsISupports
     void unlock();
 };
 
 /**
  * A interface representing a profile.
  * @note THIS INTERFACE SHOULD BE IMPLEMENTED BY THE TOOLKIT CODE ONLY! DON'T
  *       EVEN THINK ABOUT IMPLEMENTING THIS IN JAVASCRIPT!
  */
-[scriptable, uuid(7422b090-4a86-4407-972e-75468a625388)]
+[scriptable, builtinclass, uuid(7422b090-4a86-4407-972e-75468a625388)]
 interface nsIToolkitProfile : nsISupports
 {
     /**
      * The location of the profile directory.
      */
     readonly attribute nsIFile rootDir;
 
     /**
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -47,30 +47,77 @@
 #include "nsIToolkitShellService.h"
 #include "mozilla/Telemetry.h"
 
 using namespace mozilla;
 
 #define DEV_EDITION_NAME "dev-edition-default"
 #define DEFAULT_NAME "default"
 #define COMPAT_FILE NS_LITERAL_STRING("compatibility.ini")
+#define PROFILE_DB_VERSION "2"
+#define INSTALL_PREFIX "Install"
+#define INSTALL_PREFIX_LENGTH 7
+
+struct KeyValue {
+  KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
+
+  nsCString key;
+  nsCString value;
+};
+
+static bool GetStrings(const char* aString, const char* aValue,
+                       void* aClosure) {
+  nsTArray<UniquePtr<KeyValue>>* array =
+      static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
+  array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
+
+  return true;
+}
+
+/**
+ * Returns an array of the strings inside a section of an ini file.
+ */
+nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
+                                                const char* aSection) {
+  nsTArray<UniquePtr<KeyValue>> result;
+  aParser->GetStrings(aSection, &GetStrings, &result);
+  return result;
+}
 
 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
-                                   nsIFile* aLocalDir, nsToolkitProfile* aPrev)
-    : mPrev(aPrev),
-      mName(aName),
+                                   nsIFile* aLocalDir, bool aFromDB)
+    : mName(aName),
       mRootDir(aRootDir),
       mLocalDir(aLocalDir),
-      mLock(nullptr) {
+      mLock(nullptr),
+      mIndex(0),
+      mSection("Profile") {
   NS_ASSERTION(aRootDir, "No file!");
 
-  if (aPrev) {
-    aPrev->mNext = this;
-  } else {
-    nsToolkitProfileService::gService->mFirst = this;
+  RefPtr<nsToolkitProfile> prev =
+      nsToolkitProfileService::gService->mProfiles.getLast();
+  if (prev) {
+    mIndex = prev->mIndex + 1;
+  }
+  mSection.AppendInt(mIndex);
+
+  nsToolkitProfileService::gService->mProfiles.insertBack(this);
+
+  // If this profile isn't in the database already add it.
+  if (!aFromDB) {
+    nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
+    db->SetString(mSection.get(), "Name", mName.get());
+
+    bool isRelative = false;
+    nsCString descriptor;
+    nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
+                                                            &isRelative);
+
+    db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
+    db->SetString(mSection.get(), "Path", descriptor.get());
   }
 }
 
 NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
 
 NS_IMETHODIMP
 nsToolkitProfile::GetRootDir(nsIFile** aResult) {
   NS_ADDREF(*aResult = mRootDir);
@@ -101,34 +148,39 @@ nsToolkitProfile::SetName(const nsACStri
   // profile no longer the dev-edition default.
   if (mName.EqualsLiteral(DEV_EDITION_NAME) &&
       nsToolkitProfileService::gService->mDevEditionDefault == this) {
     nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
   }
 
   mName = aName;
 
+  nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
+      mSection.get(), "Name", mName.get());
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Setting the name to the dev-edition default profile name will cause this
   // profile to become the dev-edition default.
   if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
       !nsToolkitProfileService::gService->mDevEditionDefault) {
     nsToolkitProfileService::gService->mDevEditionDefault = this;
   }
 
   return NS_OK;
 }
 
 nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
                                           bool aInBackground) {
   NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
 
   if (mLock) return NS_ERROR_FILE_IS_LOCKED;
 
-  if (!mPrev && !mNext && nsToolkitProfileService::gService->mFirst != this)
+  if (!isInList()) {
     return NS_ERROR_NOT_INITIALIZED;
+  }
 
   if (aRemoveFiles) {
     // Check if another instance is using this profile.
     nsCOMPtr<nsIProfileLock> lock;
     nsresult rv = Lock(nullptr, getter_AddRefs(lock));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIFile> rootDir(mRootDir);
@@ -155,25 +207,39 @@ nsresult nsToolkitProfile::RemoveInterna
       nsCOMPtr<nsIEventTarget> target =
           do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       target->Dispatch(runnable, NS_DISPATCH_NORMAL);
     } else {
       runnable->Run();
     }
   }
 
-  if (mPrev)
-    mPrev->mNext = mNext;
-  else
-    nsToolkitProfileService::gService->mFirst = mNext;
+  nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
+  db->DeleteSection(mSection.get());
 
-  if (mNext) mNext->mPrev = mPrev;
+  // We make some assumptions that the profile's index in the database is based
+  // on its position in the linked list. Removing a profile means we have to fix
+  // the index of later profiles in the list. The easiest way to do that is just
+  // to move the last profile into the profile's position and just update its
+  // index.
+  RefPtr<nsToolkitProfile> last =
+      nsToolkitProfileService::gService->mProfiles.getLast();
+  if (last != this) {
+    // Update the section in the db.
+    last->mIndex = mIndex;
+    db->RenameSection(last->mSection.get(), mSection.get());
+    last->mSection = mSection;
 
-  mPrev = nullptr;
-  mNext = nullptr;
+    if (last != getNext()) {
+      last->remove();
+      setNext(last);
+    }
+  }
+
+  remove();
 
   if (nsToolkitProfileService::gService->mNormalDefault == this) {
     nsToolkitProfileService::gService->mNormalDefault = nullptr;
   }
   if (nsToolkitProfileService::gService->mDevEditionDefault == this) {
     nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
   }
   if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
@@ -308,17 +374,20 @@ nsToolkitProfileService::nsToolkitProfil
       mStartupReason(NS_LITERAL_STRING("unknown")),
       mMaybeLockProfile(false) {
 #ifdef MOZ_DEV_EDITION
   mUseDevEditionProfile = true;
 #endif
   gService = this;
 }
 
-nsToolkitProfileService::~nsToolkitProfileService() { gService = nullptr; }
+nsToolkitProfileService::~nsToolkitProfileService() {
+  gService = nullptr;
+  mProfiles.clear();
+}
 
 void nsToolkitProfileService::CompleteStartup() {
   if (!mStartupProfileSelected) {
     return;
   }
 
   ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
             mStartupReason);
@@ -330,17 +399,17 @@ void nsToolkitProfileService::CompleteSt
       return;
     }
 
     bool isDefaultApp;
     nsresult rv = shell->IsDefaultApplication(&isDefaultApp);
     NS_ENSURE_SUCCESS_VOID(rv);
 
     if (isDefaultApp) {
-      mInstallData.SetString(mInstallHash.get(), "Locked", "1");
+      mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
       Flush();
     }
   }
 }
 
 // Tests whether the passed profile was last used by this install.
 bool nsToolkitProfileService::IsProfileForCurrentInstall(
     nsIToolkitProfile* aProfile) {
@@ -435,120 +504,169 @@ bool nsToolkitProfileService::MaybeMakeD
   // Cache the installs that use the profile.
   nsTArray<nsCString> inUseInstalls;
 
   // See if the profile is already in use by an install that hasn't locked it.
   for (uint32_t i = 0; i < installs.Length(); i++) {
     const nsCString& install = installs[i];
 
     nsCString path;
-    rv = mInstallData.GetString(install.get(), "Default", path);
+    rv = mProfileDB.GetString(install.get(), "Default", path);
     if (NS_FAILED(rv)) {
       continue;
     }
 
     // Is this install using the profile we care about?
     if (!descriptor.Equals(path)) {
       continue;
     }
 
     // Is this profile locked to this other install?
     nsCString isLocked;
-    rv = mInstallData.GetString(install.get(), "Locked", isLocked);
+    rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
     if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
       return false;
     }
 
     inUseInstalls.AppendElement(install);
   }
 
   // At this point we've decided to take the profile. Strip it from other
   // installs.
   for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
     // Removing the default setting entirely will make the install go through
     // the first run process again at startup and create itself a new profile.
-    mInstallData.DeleteString(inUseInstalls[i].get(), "Default");
+    mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
   }
 
   // Set this as the default profile for this install.
   SetDefaultProfile(aProfile);
 
   // SetDefaultProfile will have locked this profile to this install so no
   // other installs will steal it, but this was auto-selected so we want to
   // unlock it so that other installs can potentially take it.
-  mInstallData.DeleteString(mInstallHash.get(), "Locked");
+  mProfileDB.DeleteString(mInstallSection.get(), "Locked");
 
   // Persist the changes.
   Flush();
 
   // Once XPCOM is available check if this is the default application and if so
   // lock the profile again.
   mMaybeLockProfile = true;
 
   return true;
 }
 
+struct ImportInstallsClosure {
+  nsINIParser* backupData;
+  nsINIParser* profileDB;
+};
+
+static bool ImportInstalls(const char* aSection, void* aClosure) {
+  ImportInstallsClosure* closure =
+      static_cast<ImportInstallsClosure*>(aClosure);
+
+  nsTArray<UniquePtr<KeyValue>> strings =
+      GetSectionStrings(closure->backupData, aSection);
+  if (strings.IsEmpty()) {
+    return true;
+  }
+
+  nsCString newSection(INSTALL_PREFIX);
+  newSection.Append(aSection);
+  nsCString buffer;
+
+  for (uint32_t i = 0; i < strings.Length(); i++) {
+    closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
+                                  strings[i]->value.get());
+  }
+
+  return true;
+}
+
 nsresult nsToolkitProfileService::Init() {
   NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
   nsresult rv;
 
   rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsCString installProfilePath;
-
-  if (mUseDedicatedProfile) {
-    // Load the dedicated profiles database.
-    rv = mAppData->Clone(getter_AddRefs(mInstallFile));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mInstallFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
-    NS_ENSURE_SUCCESS(rv, rv);
+  rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    nsString installHash;
-    rv = gDirServiceProvider->GetInstallHash(installHash);
-    NS_ENSURE_SUCCESS(rv, rv);
-    CopyUTF16toUTF8(installHash, mInstallHash);
+  rv = mProfileDBFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = mInstallData.Init(mInstallFile);
-    if (NS_SUCCEEDED(rv)) {
-      // Try to find the descriptor for the default profile for this install.
-      rv = mInstallData.GetString(mInstallHash.get(), "Default",
-                                  installProfilePath);
-      // Not having a value means this install doesn't appear in installs.ini so
-      // this is the first run for this install.
-      mIsFirstRun = NS_FAILED(rv);
-    }
-  }
-
-  rv = mAppData->Clone(getter_AddRefs(mListFile));
+  rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mListFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
+  rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsINIParser parser;
+  nsAutoCString buffer;
 
   bool exists;
-  rv = mListFile->IsFile(&exists);
+  rv = mProfileDBFile->IsFile(&exists);
   if (NS_SUCCEEDED(rv) && exists) {
-    rv = parser.Init(mListFile);
+    rv = mProfileDB.Init(mProfileDBFile);
     // Init does not fail on parsing errors, only on OOM/really unexpected
     // conditions.
     if (NS_FAILED(rv)) {
       return rv;
     }
+
+    rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
+    if (NS_SUCCEEDED(rv)) {
+      mStartWithLast = !buffer.EqualsLiteral("0");
+    }
+
+    rv = mProfileDB.GetString("General", "Version", buffer);
+    if (NS_FAILED(rv)) {
+      // This is a profiles.ini written by an older version. We must restore
+      // any install data from the backup.
+      nsINIParser installDB;
+
+      rv = mInstallDBFile->IsFile(&exists);
+      if (NS_SUCCEEDED(rv) && exists &&
+          NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
+        // There is install data to import.
+        ImportInstallsClosure closure = {&installDB, &mProfileDB};
+        installDB.GetSections(&ImportInstalls, &closure);
+      }
+
+      rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  } else {
+    rv = mProfileDB.SetString("General", "StartWithLastProfile",
+                              mStartWithLast ? "1" : "0");
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  nsAutoCString buffer;
-  rv = parser.GetString("General", "StartWithLastProfile", buffer);
-  if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("0")) mStartWithLast = false;
+  nsCString installProfilePath;
+
+  if (mUseDedicatedProfile) {
+    nsString installHash;
+    rv = gDirServiceProvider->GetInstallHash(installHash);
+    NS_ENSURE_SUCCESS(rv, rv);
+    CopyUTF16toUTF8(installHash, mInstallSection);
+    mInstallSection.Insert(INSTALL_PREFIX, 0);
+
+    // Try to find the descriptor for the default profile for this install.
+    rv = mProfileDB.GetString(mInstallSection.get(), "Default",
+                              installProfilePath);
+    // Not having a value means this install doesn't appear in installs.ini so
+    // this is the first run for this install.
+    mIsFirstRun = NS_FAILED(rv);
+  }
 
   nsToolkitProfile* currentProfile = nullptr;
 
 #ifdef MOZ_DEV_EDITION
   nsCOMPtr<nsIFile> ignoreDevEditionProfile;
   rv = mAppData->Clone(getter_AddRefs(ignoreDevEditionProfile));
   if (NS_FAILED(rv)) {
     return rv;
@@ -570,32 +688,32 @@ nsresult nsToolkitProfileService::Init()
   nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
 
   unsigned int nonDevEditionProfiles = 0;
   unsigned int c = 0;
   for (c = 0; true; ++c) {
     nsAutoCString profileID("Profile");
     profileID.AppendInt(c);
 
-    rv = parser.GetString(profileID.get(), "IsRelative", buffer);
+    rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
     if (NS_FAILED(rv)) break;
 
     bool isRelative = buffer.EqualsLiteral("1");
 
     nsAutoCString filePath;
 
-    rv = parser.GetString(profileID.get(), "Path", filePath);
+    rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
     if (NS_FAILED(rv)) {
       NS_ERROR("Malformed profiles.ini: Path= not found");
       continue;
     }
 
     nsAutoCString name;
 
-    rv = parser.GetString(profileID.get(), "Name", name);
+    rv = mProfileDB.GetString(profileID.get(), "Name", name);
     if (NS_FAILED(rv)) {
       NS_ERROR("Malformed profiles.ini: Name= not found");
       continue;
     }
 
     nsCOMPtr<nsIFile> rootDir;
     rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(rootDir));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -613,21 +731,20 @@ nsresult nsToolkitProfileService::Init()
           NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir));
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = localDir->SetRelativeDescriptor(mTempData, filePath);
     } else {
       localDir = rootDir;
     }
 
-    currentProfile =
-        new nsToolkitProfile(name, rootDir, localDir, currentProfile);
+    currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
     NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
 
-    rv = parser.GetString(profileID.get(), "Default", buffer);
+    rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
     if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
       mNormalDefault = currentProfile;
     }
 
     // Is this the default profile for this install?
     if (mUseDedicatedProfile && !mDedicatedProfile &&
         installProfilePath.Equals(filePath)) {
       // Found a profile for this install.
@@ -639,17 +756,17 @@ nsresult nsToolkitProfileService::Init()
     } else {
       nonDevEditionProfiles++;
       autoSelectProfile = currentProfile;
     }
   }
 
   // If there is only one non-dev-edition profile then mark it as the default.
   if (!mNormalDefault && nonDevEditionProfiles == 1) {
-    mNormalDefault = autoSelectProfile;
+    SetNormalDefault(autoSelectProfile);
   }
 
   if (!mUseDedicatedProfile) {
     if (mUseDevEditionProfile) {
       // When using the separate dev-edition profile not finding it means this
       // is a first run.
       mIsFirstRun = !mDevEditionDefault;
     } else {
@@ -659,30 +776,33 @@ nsresult nsToolkitProfileService::Init()
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
   if (mStartWithLast != aValue) {
+    nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
+                                       mStartWithLast ? "1" : "0");
+    NS_ENSURE_SUCCESS(rv, rv);
     mStartWithLast = aValue;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
   *aResult = mStartWithLast;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
-  *aResult = new ProfileEnumerator(this->mFirst);
+  *aResult = new ProfileEnumerator(mProfiles.getFirst());
   if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
 
   NS_ADDREF(*aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult) {
@@ -691,17 +811,17 @@ nsToolkitProfileService::ProfileEnumerat
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
   if (!mCurrent) return NS_ERROR_FAILURE;
 
   NS_ADDREF(*aResult = mCurrent);
 
-  mCurrent = mCurrent->mNext;
+  mCurrent = mCurrent->getNext();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::GetCurrentProfile(nsIToolkitProfile** aResult) {
   NS_IF_ADDREF(*aResult = mCurrent);
   return NS_OK;
 }
@@ -717,48 +837,69 @@ nsToolkitProfileService::GetDefaultProfi
     NS_IF_ADDREF(*aResult = mDevEditionDefault);
     return NS_OK;
   }
 
   NS_IF_ADDREF(*aResult = mNormalDefault);
   return NS_OK;
 }
 
+void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
+  if (mNormalDefault == aProfile) {
+    return;
+  }
+
+  if (mNormalDefault) {
+    nsToolkitProfile* profile =
+        static_cast<nsToolkitProfile*>(mNormalDefault.get());
+    mProfileDB.DeleteString(profile->mSection.get(), "Default");
+  }
+
+  mNormalDefault = aProfile;
+
+  if (mNormalDefault) {
+    nsToolkitProfile* profile =
+        static_cast<nsToolkitProfile*>(mNormalDefault.get());
+    mProfileDB.SetString(profile->mSection.get(), "Default", "1");
+  }
+}
+
 NS_IMETHODIMP
 nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
   if (mUseDedicatedProfile) {
     if (mDedicatedProfile != aProfile) {
       if (!aProfile) {
         // Setting this to the empty string means no profile will be found on
         // startup but we'll recognise that this install has been used
         // previously.
-        mInstallData.SetString(mInstallHash.get(), "Default", "");
+        mProfileDB.SetString(mInstallSection.get(), "Default", "");
       } else {
         nsCString profilePath;
         nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        mInstallData.SetString(mInstallHash.get(), "Default",
-                               profilePath.get());
+        mProfileDB.SetString(mInstallSection.get(), "Default",
+                             profilePath.get());
       }
       mDedicatedProfile = aProfile;
 
       // Some kind of choice has happened here, lock this profile to this
       // install.
-      mInstallData.SetString(mInstallHash.get(), "Locked", "1");
+      mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
     }
     return NS_OK;
   }
 
   if (mUseDevEditionProfile && aProfile != mDevEditionDefault) {
     // The separate profile is hardcoded.
     return NS_ERROR_FAILURE;
   }
 
-  mNormalDefault = aProfile;
+  SetNormalDefault(aProfile);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::GetCreatedAlternateProfile(bool* aResult) {
   *aResult = mCreatedAlternateProfile;
   return NS_OK;
 }
@@ -808,17 +949,17 @@ nsresult nsToolkitProfileService::Create
   nsresult rv = CreateUniqueProfile(nullptr, name, aResult);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mUseDedicatedProfile) {
     SetDefaultProfile(mCurrent);
   } else if (mUseDevEditionProfile) {
     mDevEditionDefault = mCurrent;
   } else {
-    mNormalDefault = mCurrent;
+    SetNormalDefault(mCurrent);
   }
 
   return NS_OK;
 }
 
 /**
  * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
  * See nsIToolkitProfileService.idl.
@@ -837,19 +978,19 @@ nsToolkitProfileService::SelectStartupPr
 
   for (int i = 0; i < argc; i++) {
     allocated[i].reset(ToNewCString(aArgv[i]));
     argv[i] = allocated[i].get();
   }
   argv[argc] = nullptr;
 
   bool wasDefault;
-  nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir,
-                                     aLocalDir, aProfile, aDidCreate,
-                                     &wasDefault);
+  nsresult rv =
+      SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
+                           aProfile, aDidCreate, &wasDefault);
 
   // Since we were called outside of the normal startup path complete any
   // startup tasks.
   if (NS_SUCCEEDED(rv)) {
     CompleteStartup();
   }
 
   return rv;
@@ -1158,20 +1299,22 @@ nsresult nsToolkitProfileService::Select
     }
 
     rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
     if (NS_SUCCEEDED(rv)) {
       // If there is only one profile and it isn't meant to be the profile that
       // older versions of Firefox use then we must create a default profile
       // for older versions of Firefox to avoid the existing profile being
       // auto-selected.
-      if ((mUseDedicatedProfile || mUseDevEditionProfile) && mFirst &&
-          !mFirst->mNext) {
+      if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
+          mProfiles.getFirst() == mProfiles.getLast()) {
+        nsCOMPtr<nsIToolkitProfile> newProfile;
         CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
-                      getter_AddRefs(mNormalDefault));
+                      getter_AddRefs(newProfile));
+        SetNormalDefault(newProfile);
       }
 
       Flush();
 
       if (mCreatedAlternateProfile) {
         mStartupReason = NS_LITERAL_STRING("firstrun-skipped-default");
       } else {
         mStartupReason = NS_LITERAL_STRING("firstrun-created-default");
@@ -1244,32 +1387,32 @@ nsresult nsToolkitProfileService::Create
  * current profile and if the old profile was default making the new profile
  * default as well.
  */
 nsresult nsToolkitProfileService::ApplyResetProfile(
     nsIToolkitProfile* aOldProfile) {
   // If the old profile would have been the default for old installs then mark
   // the new profile as such.
   if (mNormalDefault == aOldProfile) {
-    mNormalDefault = mCurrent;
+    SetNormalDefault(mCurrent);
   }
 
   if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
     bool wasLocked = false;
     nsCString val;
     if (NS_SUCCEEDED(
-            mInstallData.GetString(mInstallHash.get(), "Locked", val))) {
+            mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
       wasLocked = val.Equals("1");
     }
 
     SetDefaultProfile(mCurrent);
 
     // Make the locked state match if necessary.
     if (!wasLocked) {
-      mInstallData.DeleteString(mInstallHash.get(), "Locked");
+      mProfileDB.DeleteString(mInstallSection.get(), "Locked");
     }
   }
 
   nsCString name;
   nsresult rv = aOldProfile->GetName(name);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aOldProfile->Remove(false);
@@ -1281,47 +1424,43 @@ nsresult nsToolkitProfileService::ApplyR
   NS_ENSURE_SUCCESS(rv, rv);
 
   return Flush();
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::GetProfileByName(const nsACString& aName,
                                           nsIToolkitProfile** aResult) {
-  nsToolkitProfile* curP = mFirst;
-  while (curP) {
-    if (curP->mName.Equals(aName)) {
-      NS_ADDREF(*aResult = curP);
+  for (RefPtr<nsToolkitProfile> profile : mProfiles) {
+    if (profile->mName.Equals(aName)) {
+      NS_ADDREF(*aResult = profile);
       return NS_OK;
     }
-    curP = curP->mNext;
   }
 
   return NS_ERROR_FAILURE;
 }
 
 /**
  * Finds a profile from the database that uses the given root and local
  * directories.
  */
 void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
                                               nsIFile* aLocalDir,
                                               nsIToolkitProfile** aResult) {
-  nsToolkitProfile* curP = mFirst;
-  while (curP) {
+  for (RefPtr<nsToolkitProfile> profile : mProfiles) {
     bool equal;
-    nsresult rv = curP->mRootDir->Equals(aRootDir, &equal);
+    nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
     if (NS_SUCCEEDED(rv) && equal) {
-      rv = curP->mLocalDir->Equals(aLocalDir, &equal);
+      rv = profile->mLocalDir->Equals(aLocalDir, &equal);
       if (NS_SUCCEEDED(rv) && equal) {
-        NS_ADDREF(*aResult = curP);
+        NS_ADDREF(*aResult = profile);
         return;
       }
     }
-    curP = curP->mNext;
   }
 }
 
 nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
                             nsIProfileUnlocker** aUnlocker,
                             nsIProfileLock** aResult) {
   RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
   if (!lock) return NS_ERROR_OUT_OF_MEMORY;
@@ -1444,25 +1583,18 @@ nsToolkitProfileService::CreateProfile(n
   }
 
   // We created a new profile dir. Let's store a creation timestamp.
   // Note that this code path does not apply if the profile dir was
   // created prior to launching.
   rv = CreateTimesInternal(rootDir);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsToolkitProfile* last = mFirst.get();
-  if (last) {
-    while (last->mNext) {
-      last = last->mNext;
-    }
-  }
-
   nsCOMPtr<nsIToolkitProfile> profile =
-      new nsToolkitProfile(aName, rootDir, localDir, last);
+      new nsToolkitProfile(aName, rootDir, localDir, false);
   if (!profile) return NS_ERROR_OUT_OF_MEMORY;
 
   if (aName.Equals(DEV_EDITION_NAME)) {
     mDevEditionDefault = profile;
   }
 
   profile.forget(aResult);
   return NS_OK;
@@ -1491,27 +1623,32 @@ bool nsToolkitProfileService::IsSnapEnvi
 struct FindInstallsClosure {
   nsINIParser* installData;
   nsTArray<nsCString>* installs;
 };
 
 static bool FindInstalls(const char* aSection, void* aClosure) {
   FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
 
+  // Check if the section starts with "Install"
+  if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
+    return true;
+  }
+
   nsCString install(aSection);
   closure->installs->AppendElement(install);
 
   return true;
 }
 
 nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
   nsTArray<nsCString> result;
-  FindInstallsClosure closure = {&mInstallData, &result};
+  FindInstallsClosure closure = {&mProfileDB, &result};
 
-  mInstallData.GetSections(&FindInstalls, &closure);
+  mProfileDB.GetSections(&FindInstalls, &closure);
 
   return result;
 }
 
 nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
   nsresult rv = NS_ERROR_FAILURE;
   nsCOMPtr<nsIFile> creationLog;
   rv = aProfileDir->Clone(getter_AddRefs(creationLog));
@@ -1540,94 +1677,81 @@ nsresult nsToolkitProfileService::Create
   PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
   PR_Close(writeFile);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
   *aResult = 0;
-  nsToolkitProfile* profile = mFirst;
-  while (profile) {
+  for (nsToolkitProfile* profile : mProfiles) {
+    Unused << profile;
     (*aResult)++;
-    profile = profile->mNext;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsToolkitProfileService::Flush() {
   nsresult rv;
 
+  // If we aren't using dedicated profiles then nothing about the list of
+  // installs can have changed, so no need to update the backup.
   if (mUseDedicatedProfile) {
-    rv = mInstallData.WriteToFile(mInstallFile);
-    NS_ENSURE_SUCCESS(rv, rv);
+    // Export the installs to the backup.
+    nsTArray<nsCString> installs = GetKnownInstalls();
+
+    if (!installs.IsEmpty()) {
+      nsCString data;
+      nsCString buffer;
+
+      for (uint32_t i = 0; i < installs.Length(); i++) {
+        nsTArray<UniquePtr<KeyValue>> strings =
+            GetSectionStrings(&mProfileDB, installs[i].get());
+        if (strings.IsEmpty()) {
+          continue;
+        }
+
+        // Strip "Install" from the start.
+        const nsDependentCSubstring& install =
+            Substring(installs[i], INSTALL_PREFIX_LENGTH);
+        data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
+
+        for (uint32_t j = 0; j < strings.Length(); j++) {
+          data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
+                            strings[j]->value.get());
+        }
+
+        data.Append("\n");
+      }
+
+      FILE* writeFile;
+      rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      uint32_t length = data.Length();
+      if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
+        fclose(writeFile);
+        return NS_ERROR_UNEXPECTED;
+      }
+
+      fclose(writeFile);
+    } else {
+      rv = mInstallDBFile->Remove(false);
+      if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
+          rv != NS_ERROR_FILE_NOT_FOUND) {
+        return rv;
+      }
+    }
   }
 
-  // Errors during writing might cause unhappy semi-written files.
-  // To avoid this, write the entire thing to a buffer, then write
-  // that buffer to disk.
-
-  uint32_t pCount = 0;
-  nsToolkitProfile* cur;
-
-  for (cur = mFirst; cur != nullptr; cur = cur->mNext) ++pCount;
-
-  uint32_t length;
-  const int bufsize = 100 + MAXPATHLEN * pCount;
-  auto buffer = MakeUnique<char[]>(bufsize);
-
-  char* pos = buffer.get();
-  char* end = pos + bufsize;
-
-  pos += snprintf(pos, end - pos,
-                  "[General]\n"
-                  "StartWithLastProfile=%s\n\n",
-                  mStartWithLast ? "1" : "0");
-
-  nsAutoCString path;
-  cur = mFirst;
-  pCount = 0;
-
-  while (cur) {
-    bool isRelative;
-    nsresult rv = GetProfileDescriptor(cur, path, &isRelative);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    pos +=
-        snprintf(pos, end - pos,
-                 "[Profile%u]\n"
-                 "Name=%s\n"
-                 "IsRelative=%s\n"
-                 "Path=%s\n",
-                 pCount, cur->mName.get(), isRelative ? "1" : "0", path.get());
-
-    if (cur == mNormalDefault) {
-      pos += snprintf(pos, end - pos, "Default=1\n");
-    }
-
-    pos += snprintf(pos, end - pos, "\n");
-
-    cur = cur->mNext;
-    ++pCount;
-  }
-
-  FILE* writeFile;
-  rv = mListFile->OpenANSIFileDesc("w", &writeFile);
+  rv = mProfileDB.WriteToFile(mProfileDBFile);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  length = pos - buffer.get();
-
-  if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) {
-    fclose(writeFile);
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  fclose(writeFile);
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)
 
 NS_IMETHODIMP
 nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
                                         void** aResult) {
--- a/toolkit/profile/nsToolkitProfileService.h
+++ b/toolkit/profile/nsToolkitProfileService.h
@@ -10,39 +10,41 @@
 
 #include "nsIToolkitProfileService.h"
 #include "nsIToolkitProfile.h"
 #include "nsIFactory.h"
 #include "nsSimpleEnumerator.h"
 #include "nsProfileLock.h"
 #include "nsINIParser.h"
 
-class nsToolkitProfile final : public nsIToolkitProfile {
+class nsToolkitProfile final
+    : public nsIToolkitProfile,
+      public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSITOOLKITPROFILE
 
   friend class nsToolkitProfileService;
-  RefPtr<nsToolkitProfile> mNext;
-  nsToolkitProfile* mPrev;
 
  private:
   ~nsToolkitProfile() = default;
 
   nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
-                   nsIFile* aLocalDir, nsToolkitProfile* aPrev);
+                   nsIFile* aLocalDir, bool aFromDB);
 
   nsresult RemoveInternal(bool aRemoveFiles, bool aInBackground);
 
   friend class nsToolkitProfileLock;
 
   nsCString mName;
   nsCOMPtr<nsIFile> mRootDir;
   nsCOMPtr<nsIFile> mLocalDir;
   nsIProfileLock* mLock;
+  uint32_t mIndex;
+  nsCString mSection;
 };
 
 class nsToolkitProfileLock final : public nsIProfileLock {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPROFILELOCK
 
   nsresult Init(nsToolkitProfile* aProfile, nsIProfileUnlocker** aUnlocker);
@@ -98,46 +100,47 @@ class nsToolkitProfileService final : pu
 
   nsresult GetProfileDescriptor(nsIToolkitProfile* aProfile,
                                 nsACString& aDescriptor, bool* aIsRelative);
   bool IsProfileForCurrentInstall(nsIToolkitProfile* aProfile);
   void ClearProfileFromOtherInstalls(nsIToolkitProfile* aProfile);
   bool MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile* aProfile);
   bool IsSnapEnvironment();
   nsresult CreateDefaultProfile(nsIToolkitProfile** aResult);
+  void SetNormalDefault(nsIToolkitProfile* aProfile);
 
   // Returns the known install hashes from the installs database. Modifying the
   // installs database is safe while iterating the returned array.
   nsTArray<nsCString> GetKnownInstalls();
 
   // Tracks whether SelectStartupProfile has been called.
   bool mStartupProfileSelected;
-  // The first profile in a linked list of profiles loaded from profiles.ini.
-  RefPtr<nsToolkitProfile> mFirst;
+  // The  profiles loaded from profiles.ini.
+  mozilla::LinkedList<RefPtr<nsToolkitProfile>> mProfiles;
   // The profile selected for use at startup, if it exists in profiles.ini.
   nsCOMPtr<nsIToolkitProfile> mCurrent;
   // The profile selected for this install in installs.ini.
   nsCOMPtr<nsIToolkitProfile> mDedicatedProfile;
   // The default profile used by non-dev-edition builds.
   nsCOMPtr<nsIToolkitProfile> mNormalDefault;
   // The profile used if mUseDevEditionProfile is true (the default on
   // dev-edition builds).
   nsCOMPtr<nsIToolkitProfile> mDevEditionDefault;
   // The directory that holds profiles.ini and profile directories.
   nsCOMPtr<nsIFile> mAppData;
   // The directory that holds the cache files for profiles.
   nsCOMPtr<nsIFile> mTempData;
   // The location of profiles.ini.
-  nsCOMPtr<nsIFile> mListFile;
+  nsCOMPtr<nsIFile> mProfileDBFile;
   // The location of installs.ini.
-  nsCOMPtr<nsIFile> mInstallFile;
-  // The data loaded from installs.ini.
-  nsINIParser mInstallData;
-  // The install hash for the currently running install.
-  nsCString mInstallHash;
+  nsCOMPtr<nsIFile> mInstallDBFile;
+  // The data loaded from profiles.ini.
+  nsINIParser mProfileDB;
+  // The section in the profiles db for the current install.
+  nsCString mInstallSection;
   // Whether to start with the selected profile by default.
   bool mStartWithLast;
   // True if during startup it appeared that this is the first run.
   bool mIsFirstRun;
   // True if the default profile is the separate dev-edition-profile.
   bool mUseDevEditionProfile;
   // True if this install should use a dedicated default profile.
   const bool mUseDedicatedProfile;
--- a/toolkit/profile/xpcshell/head.js
+++ b/toolkit/profile/xpcshell/head.js
@@ -164,17 +164,17 @@ function writeCompatibilityIni(dir, appD
 function writeProfilesIni(profileData) {
   let target = gDataHome.clone();
   target.append("profiles.ini");
 
   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                 getService(Ci.nsIINIParserFactory);
   let ini = factory.createINIParser().QueryInterface(Ci.nsIINIParserWriter);
 
-  const { options = {}, profiles = [] } = profileData;
+  const { options = {}, profiles = [], installs = null } = profileData;
 
   let { startWithLastProfile = true } = options;
   ini.setString("General", "StartWithLastProfile", startWithLastProfile ? "1" : "0");
 
   for (let i = 0; i < profiles.length; i++) {
     let profile = profiles[i];
     let section = `Profile${i}`;
 
@@ -182,16 +182,31 @@ function writeProfilesIni(profileData) {
     ini.setString(section, "IsRelative", 1);
     ini.setString(section, "Path", profile.path);
 
     if (profile.default) {
       ini.setString(section, "Default", "1");
     }
   }
 
+  if (installs) {
+    ini.setString("General", "Version", "2");
+
+    for (let hash of Object.keys(installs)) {
+      ini.setString(`Install${hash}`, "Default", installs[hash].default);
+      if ("locked" in installs[hash]) {
+        ini.setString(`Install${hash}`, "Locked", installs[hash].locked ? "1" : "0");
+      }
+    }
+
+    writeInstallsIni({ installs });
+  } else {
+    writeInstallsIni(null);
+  }
+
   ini.writeFile(target);
 }
 
 /**
  * Reads the existing profiles.ini into the same structure as that accepted by
  * writeProfilesIni above. The profiles property is sorted according to name
  * because the order is irrelevant and it makes testing easier if we can make
  * that assumption.
@@ -200,66 +215,98 @@ function readProfilesIni() {
   let target = gDataHome.clone();
   target.append("profiles.ini");
 
   let profileData = {
     options: {
       startWithLastProfile: true,
     },
     profiles: [],
+    installs: null,
   };
 
   if (!target.exists()) {
     return profileData;
   }
 
   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                 getService(Ci.nsIINIParserFactory);
   let ini = factory.createINIParser(target);
 
   profileData.options.startWithLastProfile = safeGet(ini, "General", "StartWithLastProfile") == "1";
-
-  for (let i = 0; true; i++) {
-    let section = `Profile${i}`;
-
-    let isRelative = safeGet(ini, section, "IsRelative");
-    if (isRelative === null) {
-      break;
-    }
-    Assert.equal(isRelative, "1", "Paths should always be relative in these tests.");
+  if (safeGet(ini, "General", "Version") == "2") {
+    profileData.installs = {};
+  }
 
-    let profile = {
-      name: safeGet(ini, section, "Name"),
-      path: safeGet(ini, section, "Path"),
-    };
+  let sections = ini.getSections();
+  while (sections.hasMore()) {
+    let section = sections.getNext();
 
-    try {
-      profile.default = ini.getString(section, "Default") == "1";
-      Assert.ok(profile.default, "The Default value is only written when true.");
-    } catch (e) {
-      profile.default = false;
+    if (section == "General") {
+      continue;
     }
 
-    profileData.profiles.push(profile);
+    if (section.startsWith("Profile")) {
+      let isRelative = safeGet(ini, section, "IsRelative");
+      if (isRelative === null) {
+        break;
+      }
+      Assert.equal(isRelative, "1", "Paths should always be relative in these tests.");
+
+      let profile = {
+        name: safeGet(ini, section, "Name"),
+        path: safeGet(ini, section, "Path"),
+      };
+
+      try {
+        profile.default = ini.getString(section, "Default") == "1";
+        Assert.ok(profile.default, "The Default value is only written when true.");
+      } catch (e) {
+        profile.default = false;
+      }
+
+      profileData.profiles.push(profile);
+    }
+
+    if (section.startsWith("Install")) {
+      Assert.ok(profileData.installs, "Should only see an install section if the ini version was correct.");
+
+      profileData.installs[section.substring(7)] = {
+        default: safeGet(ini, section, "Default"),
+      };
+
+      let locked = safeGet(ini, section, "Locked");
+      if (locked !== null) {
+        profileData.installs[section.substring(7)].locked = locked;
+      }
+    }
   }
 
   profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
 
   return profileData;
 }
 
 /**
  * Writes an installs.ini based on the supplied data. Should be an object with
  * keys for every installation hash each mapping to an object. Each object
  * should have a default property for the relative path to the profile.
  */
 function writeInstallsIni(installData) {
   let target = gDataHome.clone();
   target.append("installs.ini");
 
+  if (!installData) {
+    try {
+      target.remove(false);
+    } catch (e) {
+    }
+    return;
+  }
+
   const { installs = {} } = installData;
 
   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                 getService(Ci.nsIINIParserFactory);
   let ini = factory.createINIParser(null).QueryInterface(Ci.nsIINIParserWriter);
 
   for (let hash of Object.keys(installs)) {
     ini.setString(hash, "Default", installs[hash].default);
@@ -278,54 +325,74 @@ function readInstallsIni() {
   let target = gDataHome.clone();
   target.append("installs.ini");
 
   let installData = {
     installs: {},
   };
 
   if (!target.exists()) {
+    dump("Missing installs.ini\n");
     return installData;
   }
 
   let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
                 getService(Ci.nsIINIParserFactory);
   let ini = factory.createINIParser(target);
 
   let sections = ini.getSections();
   while (sections.hasMore()) {
     let hash = sections.getNext();
     if (hash != "General") {
       installData.installs[hash] = {
         default: safeGet(ini, hash, "Default"),
-        locked: safeGet(ini, hash, "Locked") == 1,
       };
+
+      let locked = safeGet(ini, hash, "Locked");
+      if (locked !== null) {
+        installData.installs[hash].locked = locked;
+      }
     }
   }
 
   return installData;
 }
 
 /**
+ * Check that the backup data in installs.ini matches the install data in
+ * profiles.ini.
+ */
+function checkBackup(profileData = readProfilesIni(), installData = readInstallsIni()) {
+  if (!profileData.installs) {
+    // If the profiles db isn't of the right version we wouldn't expect the
+    // backup to be accurate.
+    return;
+  }
+
+  Assert.deepEqual(profileData.installs, installData.installs, "Backup installs.ini should match installs in profiles.ini");
+}
+
+/**
  * Checks that the profile service seems to have the right data in it compared
  * to profile and install data structured as in the above functions.
  */
-function checkProfileService(profileData = readProfilesIni(), installData = readInstallsIni()) {
+function checkProfileService(profileData = readProfilesIni(), verifyBackup = true) {
   let service = getProfileService();
 
   let serviceProfiles = Array.from(service.profiles);
 
   Assert.equal(serviceProfiles.length, profileData.profiles.length, "Should be the same number of profiles.");
 
   // Sort to make matching easy.
   serviceProfiles.sort((a, b) => a.name.localeCompare(b.name));
   profileData.profiles.sort((a, b) => a.name.localeCompare(b.name));
 
   let hash = xreDirProvider.getInstallHash();
-  let defaultPath = hash in installData.installs ? installData.installs[hash].default : null;
+  let defaultPath = (profileData.installs && hash in profileData.installs) ?
+                    profileData.installs[hash].default : null;
   let dedicatedProfile = null;
   let snapProfile = null;
 
   for (let i = 0; i < serviceProfiles.length; i++) {
     let serviceProfile = serviceProfiles[i];
     let expectedProfile = profileData.profiles[i];
 
     Assert.equal(serviceProfile.name, expectedProfile.name, "Should have the same name.");
@@ -347,16 +414,20 @@ function checkProfileService(profileData
     }
   }
 
   if (gIsSnap) {
     Assert.equal(service.defaultProfile, snapProfile, "Should have seen the right profile selected.");
   } else {
     Assert.equal(service.defaultProfile, dedicatedProfile, "Should have seen the right profile selected.");
   }
+
+  if (verifyBackup) {
+    checkBackup(profileData);
+  }
 }
 
 /**
  * Asynchronously reads an nsIFile from disk.
  */
 async function readFile(file) {
   let decoder = new TextDecoder();
   let data = await OS.File.read(file.path);
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_check_backup.js
@@ -0,0 +1,41 @@
+/*
+ * Tests that when the profiles DB is missing the install data we reload it.
+ */
+
+add_task(async () => {
+  let hash = xreDirProvider.getInstallHash();
+
+  let profileData = {
+    options: {
+      startWithLastProfile: true,
+    },
+    profiles: [{
+      name: "Profile1",
+      path: "Path1",
+    }, {
+      name: "Profile2",
+      path: "Path2",
+    }],
+  };
+
+  let installs = {
+    [hash]: {
+      default: "Path2",
+    },
+  };
+
+  writeProfilesIni(profileData);
+  writeInstallsIni({ installs });
+
+  let { profile, didCreate } = selectStartupProfile();
+  checkStartupReason("default");
+
+  let service = getProfileService();
+  // Should have added the backup data to the service, check that is true.
+  profileData.installs = installs;
+  checkProfileService(profileData);
+
+  Assert.ok(!didCreate, "Should not have created a new profile.");
+  Assert.equal(profile.name, "Profile2", "Should have selected the right profile");
+  Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
+});
--- a/toolkit/profile/xpcshell/test_claim_locked.js
+++ b/toolkit/profile/xpcshell/test_claim_locked.js
@@ -9,38 +9,34 @@ add_task(async () => {
   writeCompatibilityIni(defaultProfile);
 
   writeProfilesIni({
     profiles: [{
       name: PROFILE_DEFAULT,
       path: defaultProfile.leafName,
       default: true,
     }],
-  });
-
-  let hash = xreDirProvider.getInstallHash();
-  writeProfilesIni({
     installs: {
       other: {
         default: defaultProfile.leafName,
         locked: true,
       },
     },
   });
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
 
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
 
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be two known installs.");
-  Assert.notEqual(installData.installs[hash].default, defaultProfile.leafName, "Should not have marked the original default profile as the default for this install.");
-  Assert.ok(installData.installs[hash].locked, "Should have locked as we created this profile for this install.");
+  let hash = xreDirProvider.getInstallHash();
+  Assert.equal(Object.keys(profileData.installs).length, 2, "Should be two known installs.");
+  Assert.notEqual(profileData.installs[hash].default, defaultProfile.leafName, "Should not have marked the original default profile as the default for this install.");
+  Assert.ok(profileData.installs[hash].locked, "Should have locked as we created this profile for this install.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   Assert.ok(didCreate, "Should have created a new profile.");
   Assert.ok(!selectedProfile.rootDir.equals(defaultProfile), "Should be using a different directory.");
   Assert.equal(selectedProfile.name, DEDICATED_NAME);
 });
--- a/toolkit/profile/xpcshell/test_clean.js
+++ b/toolkit/profile/xpcshell/test_clean.js
@@ -13,98 +13,93 @@ add_task(async () => {
   target.leafName = "installs.ini";
   Assert.ok(!target.exists(), "installs.ini should not exist yet.");
 
   // Create a new profile to use.
   let newProfile = service.createProfile(null, "dedicated");
   service.flush();
 
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
   Assert.equal(profile.name, "dedicated", "Should have the right name.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
   // The new profile hasn't been marked as the default yet!
-  Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet.");
+  Assert.equal(Object.keys(profileData.installs).length, 0, "Should be no defaults for installs yet.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   service.defaultProfile = newProfile;
   service.flush();
 
   profileData = readProfilesIni();
-  installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   profile = profileData.profiles[0];
   Assert.equal(profile.name, "dedicated", "Should have the right name.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
   let hash = xreDirProvider.getInstallHash();
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
-  Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   let otherProfile = service.createProfile(null, "another");
   service.defaultProfile = otherProfile;
 
   service.flush();
 
   profileData = readProfilesIni();
-  installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
 
   profile = profileData.profiles[0];
   Assert.equal(profile.name, "another", "Should have the right name.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
   profile = profileData.profiles[1];
   Assert.equal(profile.name, "dedicated", "Should have the right name.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
-  Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   newProfile.remove(true);
   service.flush();
 
   profileData = readProfilesIni();
-  installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   profile = profileData.profiles[0];
   Assert.equal(profile.name, "another", "Should have the right name.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
-  Assert.equal(installData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(profileData.installs[hash].default, profileData.profiles[0].path, "Should have marked the new profile as the default for this install.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   otherProfile.remove(true);
   service.flush();
 
   profileData = readProfilesIni();
-  installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 0, "Should have the right number of profiles.");
 
   // We leave a reference to the missing profile to stop us trying to steal the
   // old-style default profile on next startup.
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 });
--- a/toolkit/profile/xpcshell/test_create_default.js
+++ b/toolkit/profile/xpcshell/test_create_default.js
@@ -4,18 +4,17 @@
 
 add_task(async () => {
   let service = getProfileService();
   let { profile, didCreate } = selectStartupProfile();
 
   checkStartupReason("firstrun-created-default");
 
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   Assert.ok(didCreate, "Should have created a new profile.");
   Assert.equal(profile, service.defaultProfile, "Should now be the default profile.");
   Assert.equal(profile.name, DEDICATED_NAME, "Should have created a new profile with the right name.");
   Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
@@ -24,10 +23,10 @@ add_task(async () => {
   Assert.equal(profile.name, "default", "Should have the right name.");
   Assert.ok(profile.default, "Should be marked as the old-style default.");
 
   profile = profileData.profiles[1];
   Assert.equal(profile.name, DEDICATED_NAME, "Should have the right name.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
   let hash = xreDirProvider.getInstallHash();
-  Assert.ok(installData.installs[hash].locked, "Should have locked the profile");
+  Assert.ok(profileData.installs[hash].locked, "Should have locked the profile");
 });
--- a/toolkit/profile/xpcshell/test_lock.js
+++ b/toolkit/profile/xpcshell/test_lock.js
@@ -16,28 +16,27 @@ add_task(async () => {
       default: true,
     }],
   });
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
   Assert.equal(profile.name, PROFILE_DEFAULT, "Should have the right name.");
   Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
   Assert.ok(profile.default, "Should be marked as the old-style default.");
 
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
-  Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
-  Assert.ok(installData.installs[hash].locked, "Should have locked as we're the default app.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
+  Assert.ok(profileData.installs[hash].locked, "Should have locked as we're the default app.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   Assert.ok(selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory.");
   Assert.equal(selectedProfile.name, PROFILE_DEFAULT);
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_missing_profilesini.js
@@ -0,0 +1,37 @@
+/**
+ * When profiles.ini is missing there isn't any point in restoring from any
+ * installs.ini, the profiles it refers to are gone anyway.
+ */
+
+add_task(async () => {
+  let hash = xreDirProvider.getInstallHash();
+
+  let installs = {
+    [hash]: {
+      default: "Path2",
+    },
+    otherhash: {
+      default: "foo",
+    },
+    anotherhash: {
+      default: "bar",
+    },
+  };
+
+  writeInstallsIni({ installs });
+
+  let { profile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-created-default");
+
+  let service = getProfileService();
+  Assert.ok(didCreate, "Should have created a new profile.");
+  Assert.equal(profile.name, DEDICATED_NAME, "Should have created the right profile");
+  Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
+
+  let profilesData = readProfilesIni();
+  Assert.equal(Object.keys(profilesData.installs).length, 1, "Should be only one known install");
+  Assert.ok(hash in profilesData.installs, "Should be the expected install.");
+  Assert.notEqual(profilesData.installs[hash].default, "Path2", "Didn't import the previous data.");
+
+  checkProfileService(profilesData);
+});
--- a/toolkit/profile/xpcshell/test_new_default.js
+++ b/toolkit/profile/xpcshell/test_new_default.js
@@ -26,17 +26,16 @@ add_task(async () => {
   });
 
   let service = getProfileService();
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
   checkStartupReason("firstrun-claimed-default");
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
   Assert.equal(profile.name, "default", "Should have the right name.");
   Assert.equal(profile.path, defaultProfile.leafName, "Should be the original non-default profile.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
@@ -46,26 +45,26 @@ add_task(async () => {
   Assert.equal(profile.path, devDefaultProfile.leafName, "Should be the original dev default profile.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
   profile = profileData.profiles[2];
   Assert.equal(profile.name, "mydefault", "Should have the right name.");
   Assert.equal(profile.path, mydefaultProfile.leafName, "Should be the original default profile.");
   Assert.ok(profile.default, "Should be marked as the old-style default.");
 
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
   if (AppConstants.MOZ_DEV_EDITION) {
-    Assert.equal(installData.installs[hash].default, devDefaultProfile.leafName, "Should have marked the original dev default profile as the default for this install.");
+    Assert.equal(profileData.installs[hash].default, devDefaultProfile.leafName, "Should have marked the original dev default profile as the default for this install.");
   } else {
-    Assert.equal(installData.installs[hash].default, mydefaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
+    Assert.equal(profileData.installs[hash].default, mydefaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
   }
 
-  Assert.ok(!installData.installs[hash].locked, "Should not be locked as we're not the default app.");
+  Assert.ok(!profileData.installs[hash].locked, "Should not be locked as we're not the default app.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   if (AppConstants.MOZ_DEV_EDITION) {
     Assert.ok(selectedProfile.rootDir.equals(devDefaultProfile), "Should be using the right directory.");
     Assert.equal(selectedProfile.name, "dev-edition-default");
   } else {
     Assert.ok(selectedProfile.rootDir.equals(mydefaultProfile), "Should be using the right directory.");
     Assert.equal(selectedProfile.name, "mydefault");
--- a/toolkit/profile/xpcshell/test_previous_dedicated.js
+++ b/toolkit/profile/xpcshell/test_previous_dedicated.js
@@ -11,38 +11,34 @@ add_task(async () => {
   writeCompatibilityIni(defaultProfile);
 
   writeProfilesIni({
     profiles: [{
       name: "default",
       path: defaultProfile.leafName,
       default: true,
     }],
-  });
-
-  writeInstallsIni({
     installs: {
       [hash]: {
         default: "foobar",
       },
     },
   });
 
   let service = getProfileService();
   testStartsProfileManager();
 
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
   Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
 
   let profile = profileData.profiles[0];
   Assert.equal(profile.name, "default", "Should have the right name.");
   Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
   Assert.ok(profile.default, "Should be marked as the old-style default.");
 
   // We keep the data here so we don't steal on the next reboot...
-  Assert.equal(Object.keys(installData.installs).length, 1, "Still list the broken reference.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Still list the broken reference.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/profile/xpcshell/test_remove.js
@@ -0,0 +1,90 @@
+/*
+ * Tests adding and removing functions correctly.
+ */
+
+function compareLists(service, knownProfiles) {
+  Assert.equal(service.profileCount, knownProfiles.length, "profileCount should be correct.");
+  let serviceProfiles = Array.from(service.profiles);
+  Assert.equal(serviceProfiles.length, knownProfiles.length, "Enumerator length should be correct.");
+
+  for (let i = 0; i < knownProfiles.length; i++) {
+    // Cannot use strictEqual here, it attempts to print out a string
+    // representation of the profile objects and on some platforms that recurses
+    // infinitely.
+    Assert.ok(serviceProfiles[i] === knownProfiles[i], `Should have the right profile in position ${i}.`);
+  }
+}
+
+function removeProfile(profiles, position) {
+  dump(`Removing profile in position ${position}.`);
+  Assert.greaterOrEqual(position, 0, "Should be removing a valid position.");
+  Assert.less(position, profiles.length, "Should be removing a valid position.");
+
+  let last = profiles.pop();
+
+  if (profiles.length == position) {
+    // We were asked to remove the last profile.
+    last.remove(false);
+    return;
+  }
+
+  profiles[position].remove(false);
+  profiles[position] = last;
+}
+
+add_task(async () => {
+  let service = getProfileService();
+  let profiles = [];
+  compareLists(service, profiles);
+
+  profiles.push(service.createProfile(null, "profile1"));
+  profiles.push(service.createProfile(null, "profile2"));
+  profiles.push(service.createProfile(null, "profile3"));
+  profiles.push(service.createProfile(null, "profile4"));
+  profiles.push(service.createProfile(null, "profile5"));
+  profiles.push(service.createProfile(null, "profile6"));
+  profiles.push(service.createProfile(null, "profile7"));
+  profiles.push(service.createProfile(null, "profile8"));
+  profiles.push(service.createProfile(null, "profile9"));
+  compareLists(service, profiles);
+
+  // Test removing the first profile.
+  removeProfile(profiles, 0);
+  compareLists(service, profiles);
+
+  // And the last profile.
+  removeProfile(profiles, profiles.length - 1);
+  compareLists(service, profiles);
+
+  // Last but one...
+  removeProfile(profiles, profiles.length - 2);
+  compareLists(service, profiles);
+
+  // Second one...
+  removeProfile(profiles, 1);
+  compareLists(service, profiles);
+
+  // Something in the middle.
+  removeProfile(profiles, 2);
+  compareLists(service, profiles);
+
+  let expectedNames = [
+    "profile9",
+    "profile7",
+    "profile5",
+    "profile4",
+  ];
+
+  let serviceProfiles = Array.from(service.profiles);
+  for (let i = 0; i < expectedNames.length; i++) {
+    Assert.equal(serviceProfiles[i].name, expectedNames[i]);
+  }
+
+  removeProfile(profiles, 0);
+  removeProfile(profiles, 0);
+  removeProfile(profiles, 0);
+  removeProfile(profiles, 0);
+
+  Assert.equal(Array.from(service.profiles).length, 0, "All profiles gone.");
+  Assert.equal(service.profileCount, 0, "All profiles gone.");
+});
--- a/toolkit/profile/xpcshell/test_remove_default.js
+++ b/toolkit/profile/xpcshell/test_remove_default.js
@@ -8,54 +8,51 @@ add_task(async () => {
   let defaultProfile = makeRandomProfileDir("default");
 
   let profilesIni = {
     profiles: [{
       name: "default",
       path: defaultProfile.leafName,
       default: true,
     }],
-  };
-  writeProfilesIni(profilesIni);
-
-  let installsIni = {
     installs: {
       [hash]: {
         default: defaultProfile.leafName,
       },
     },
   };
-  writeInstallsIni(installsIni);
+  writeProfilesIni(profilesIni);
 
   let service = getProfileService();
-  checkProfileService(profilesIni, installsIni);
+  checkProfileService(profilesIni);
 
   let { profile, didCreate } = selectStartupProfile();
   Assert.ok(!didCreate, "Should have not created a new profile.");
   Assert.equal(profile.name, "default", "Should have selected the default profile.");
   Assert.equal(profile, service.defaultProfile, "Should have selected the default profile.");
 
-  checkProfileService(profilesIni, installsIni);
+  checkProfileService(profilesIni);
 
   // In an actual run of Firefox we wouldn't be able to delete the profile in
   // use because it would be locked. But we don't actually lock the profile in
   // tests.
   profile.remove(false);
 
   Assert.ok(!service.defaultProfile, "Should no longer be a default profile.");
   Assert.equal(profile, service.currentProfile, "Should still be the profile in use.");
 
   // These are the modifications that should have been made.
   profilesIni.profiles.pop();
-  installsIni.installs[hash].default = "";
+  profilesIni.installs[hash].default = "";
 
-  checkProfileService(profilesIni, installsIni);
+  // The data isn't flushed to disk so don't check the backup here.
+  checkProfileService(profilesIni, false);
 
   service.flush();
 
   // And that should have flushed to disk correctly.
   checkProfileService();
 
   // checkProfileService doesn't differentiate between a blank default profile
   // for the install and a missing install.
-  let installs = readInstallsIni();
-  Assert.equal(installs.installs[hash].default, "", "Should be a blank default profile.");
+  profilesIni = readProfilesIni();
+  Assert.equal(profilesIni.installs[hash].default, "", "Should be a blank default profile.");
 });
--- a/toolkit/profile/xpcshell/test_select_default.js
+++ b/toolkit/profile/xpcshell/test_select_default.js
@@ -11,18 +11,16 @@ add_task(async () => {
     },
     profiles: [{
       name: "Profile1",
       path: "Path1",
     }, {
       name: "Profile3",
       path: "Path3",
     }],
-  };
-  let installData = {
     installs: {
       [hash]: {
         default: "Path2",
       },
     },
   };
 
   if (AppConstants.MOZ_DEV_EDITION) {
@@ -38,17 +36,16 @@ add_task(async () => {
     profileData.profiles.push({
       name: PROFILE_DEFAULT,
       path: "Path2",
       default: true,
     });
   }
 
   writeProfilesIni(profileData);
-  writeInstallsIni(installData);
 
   let { profile, didCreate } = selectStartupProfile();
   checkStartupReason("default");
 
   let service = getProfileService();
   checkProfileService(profileData);
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
--- a/toolkit/profile/xpcshell/test_single_profile_selected.js
+++ b/toolkit/profile/xpcshell/test_single_profile_selected.js
@@ -17,30 +17,29 @@ add_task(async () => {
     }],
   });
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
   checkStartupReason("firstrun-claimed-default");
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
   Assert.equal(profile.name, "default", "Should have the right name.");
   Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
   Assert.ok(profile.default, "Should be marked as the old-style default.");
 
-  Assert.equal(Object.keys(installData.installs).length, 1, "Should be only one known install.");
-  Assert.equal(installData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
-  Assert.ok(!installData.installs[hash].locked, "Should not have locked as we're not the default app.");
+  Assert.equal(Object.keys(profileData.installs).length, 1, "Should be only one known install.");
+  Assert.equal(profileData.installs[hash].default, defaultProfile.leafName, "Should have marked the original default profile as the default for this install.");
+  Assert.ok(!profileData.installs[hash].locked, "Should not have locked as we're not the default app.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   let service = getProfileService();
   Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
   Assert.ok(selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory.");
   Assert.equal(selectedProfile.name, "default");
 });
--- a/toolkit/profile/xpcshell/test_single_profile_unselected.js
+++ b/toolkit/profile/xpcshell/test_single_profile_unselected.js
@@ -18,29 +18,28 @@ add_task(async () => {
       path: defaultProfile.leafName,
       default: false,
     }],
   });
 
   let service = getProfileService();
 
   let profileData = readProfilesIni();
-  let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
   Assert.equal(profile.name, "default", "Should have the right name.");
   Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
-  Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet.");
+  Assert.ok(!profileData.installs, "Should be no defaults for installs yet.");
 
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
   checkStartupReason("firstrun-skipped-default");
   Assert.ok(didCreate, "Should have created a new profile.");
   Assert.ok(service.createdAlternateProfile, "Should have created an alternate profile.");
   Assert.ok(!selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory.");
   Assert.equal(selectedProfile.name, DEDICATED_NAME);
 
--- a/toolkit/profile/xpcshell/test_skip_locked_environment.js
+++ b/toolkit/profile/xpcshell/test_skip_locked_environment.js
@@ -21,31 +21,27 @@ add_task(async () => {
       default: true,
     }, {
       name: "Profile2",
       path: "Path2",
     }, {
       name: "Profile3",
       path: "Path3",
     }],
-  };
-
-  // Another install is using the profile and it is locked.
-  let installData = {
+    // Another install is using the profile and it is locked.
     installs: {
       otherinstall: {
         default: root.leafName,
         locked: true,
       },
     },
   };
 
   writeProfilesIni(profileData);
-  writeInstallsIni(installData);
-  checkProfileService(profileData, installData);
+  checkProfileService(profileData);
 
   let env = Cc["@mozilla.org/process/environment;1"].
             getService(Ci.nsIEnvironment);
   env.set("XRE_PROFILE_PATH", root.path);
   env.set("XRE_PROFILE_LOCAL_PATH", local.path);
 
   let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
   checkStartupReason("restart-skipped-default");
@@ -68,15 +64,14 @@ add_task(async () => {
 
   Assert.equal(profileData.profiles[0].name, PROFILE_DEFAULT, "Should be the right profile.");
   Assert.ok(profileData.profiles[0].default, "Should be the old default profile.");
   Assert.equal(profileData.profiles[0].path, root.leafName, "Should be the correct path.");
   Assert.equal(profileData.profiles[1].name, expectedName, "Should be the right profile.");
   Assert.ok(!profileData.profiles[1].default, "Should not be the old default profile.");