Merge mozilla-central to autoland a=merge on a CLOSED TREE
authorarthur.iakab <aiakab@mozilla.com>
Fri, 08 Mar 2019 00:14:12 +0200
changeset 520908 3d19ee7bc2d7967b730aab39594f81eb3e0c3626
parent 520907 362cf2e57ed2851c113a00d358207e27aded9cc5 (current diff)
parent 520832 af29567ecdba5441117b3f01ac2559a3473c3d6a (diff)
child 520909 91fcd9232c7e64ec113afd5f9eb183939cd52110
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland a=merge on a CLOSED TREE
browser/components/sessionstore/test/browser_preopened_apptabs.js
modules/libpref/init/all.js
toolkit/components/antitracking/AntiTrackingCommon.cpp
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1717,16 +1717,17 @@ dependencies = [
 
 [[package]]
 name = "mozurl"
 version = "0.0.1"
 dependencies = [
  "nserror 0.1.0",
  "nsstring 0.1.0",
  "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "mozversion"
 version = "0.2.0"
 dependencies = [
  "regex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -17,21 +17,16 @@
 .tab-throbber:not([busy]),
 .tab-icon-sound:not([soundplaying]):not([muted]):not([activemedia-blocked]),
 .tab-icon-sound[pinned],
 .tab-sharing-icon-overlay,
 .tab-icon-overlay {
   display: none;
 }
 
-.tab-icon-pending[preopened],
-.tab-icon-image[preopened] {
-  visibility: hidden;
-}
-
 .tab-sharing-icon-overlay[sharing]:not([selected]),
 .tab-icon-overlay[soundplaying][pinned],
 .tab-icon-overlay[muted][pinned],
 .tab-icon-overlay[activemedia-blocked][pinned],
 .tab-icon-overlay[crashed] {
   display: -moz-box;
 }
 
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -27,17 +27,16 @@ window._gBrowser = {
     Services.els.addSystemEventListener(document, "keydown", this, false);
     if (AppConstants.platform == "macosx") {
       Services.els.addSystemEventListener(document, "keypress", this, false);
     }
     window.addEventListener("sizemodechange", this);
     window.addEventListener("occlusionstatechange", this);
 
     this._setupInitialBrowserAndTab();
-    this._preopenPinnedTabs();
 
     if (Services.prefs.getBoolPref("browser.display.use_system_colors")) {
       this.tabpanels.style.backgroundColor = "-moz-default-background-color";
     } else if (Services.prefs.getIntPref("browser.display.document_color_use") == 2) {
       this.tabpanels.style.backgroundColor =
         Services.prefs.getCharPref("browser.display.background_color");
     }
 
@@ -379,41 +378,16 @@ window._gBrowser = {
     let filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
                   .createInstance(Ci.nsIWebProgress);
     filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
     this._tabListeners.set(tab, tabListener);
     this._tabFilters.set(tab, filter);
     browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
   },
 
-  _preopenPinnedTabs() {
-    let numPinnedTabs = 0;
-    if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
-      let windows = browserWindows();
-      windows.getNext();
-      let isOnlyWindow = !windows.hasMoreElements();
-      if (isOnlyWindow) {
-        numPinnedTabs = Services.prefs.getIntPref("browser.tabs.firstWindowRestore.numPinnedTabs", 0);
-      }
-    }
-
-    for (let i = 0; i < numPinnedTabs; i++) {
-      let tab = this.addTrustedTab("about:blank", {
-        skipAnimation: true,
-        noInitialLabel: true,
-        skipBackgroundNotify: true,
-        createLazyBrowser: true,
-        pinned: true,
-        isForFirstWindowRestore: true,
-      });
-
-      tab.setAttribute("preopened", "true");
-    }
-  },
-
   /**
    * BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
    * MAKE SURE TO ADD IT HERE AS WELL.
    */
   get canGoBack() {
     return this.selectedBrowser.canGoBack;
   },
 
@@ -614,68 +588,47 @@ window._gBrowser = {
   },
 
   _updateTabBarForPinnedTabs() {
     this.tabContainer._unlockTabSizing();
     this.tabContainer._positionPinnedTabs();
     this.tabContainer._updateCloseButtons();
   },
 
-  _sendPinnedTabContentMessage(aTab) {
+  _notifyPinnedStatus(aTab) {
     this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: aTab.pinned });
-  },
-
-  _notifyPinnedStatus(aTab, aDeferContentMessage = false) {
-    if (!aDeferContentMessage) {
-      this._sendPinnedTabContentMessage(aTab);
-    }
 
     let event = document.createEvent("Events");
     event.initEvent(aTab.pinned ? "TabPinned" : "TabUnpinned", true, false);
     aTab.dispatchEvent(event);
   },
 
-  _maybeUpdateNumPinnedTabsPref() {
-    if (!PrivateBrowsingUtils.isWindowPrivate(window) &&
-        BrowserWindowTracker.getTopWindow() == window) {
-      Services.prefs.setIntPref("browser.tabs.firstWindowRestore.numPinnedTabs",
-                                this._numPinnedTabs);
-    }
-  },
-
-  activatePreopenedPinnedTab(aTab) {
-    this._insertBrowser(aTab);
-    this._sendPinnedTabContentMessage(aTab);
-  },
-
   pinTab(aTab) {
     if (aTab.pinned)
       return;
 
     if (aTab.hidden)
       this.showTab(aTab);
 
     this.moveTabTo(aTab, this._numPinnedTabs);
     aTab.setAttribute("pinned", "true");
     this._updateTabBarForPinnedTabs();
     this._notifyPinnedStatus(aTab);
-    this._maybeUpdateNumPinnedTabsPref();
   },
 
   unpinTab(aTab) {
     if (!aTab.pinned)
       return;
 
     this.moveTabTo(aTab, this._numPinnedTabs - 1);
     aTab.removeAttribute("pinned");
     aTab.style.marginInlineStart = "";
     aTab._pinnedUnscrollable = false;
     this._updateTabBarForPinnedTabs();
     this._notifyPinnedStatus(aTab);
-    this._maybeUpdateNumPinnedTabsPref();
   },
 
   previewTab(aTab, aCallback) {
     let currentTab = this.selectedTab;
     try {
       // Suppress focus, ownership and selected tab changes
       this._previewMode = true;
       this.selectedTab = aTab;
@@ -2349,17 +2302,16 @@ window._gBrowser = {
     bulkOrderedOpen,
     charset,
     createLazyBrowser,
     eventDetail,
     focusUrlBar,
     forceNotRemote,
     fromExternal,
     index,
-    isForFirstWindowRestore,
     lazyTabTitle,
     name,
     nextTabParentId,
     noInitialLabel,
     noReferrer,
     opener,
     openerBrowser,
     originPrincipal,
@@ -2533,19 +2485,16 @@ window._gBrowser = {
       if (tabAfter) {
         this._updateTabsAfterInsert();
       } else {
         t._tPos = index;
       }
 
       if (pinned) {
         this._updateTabBarForPinnedTabs();
-        if (!isForFirstWindowRestore) {
-          this._maybeUpdateNumPinnedTabsPref();
-        }
       }
       this.tabContainer._setPositionalAttributes();
 
       TabBarVisibility.update();
 
       // If we don't have a preferred remote type, and we have a remote
       // opener, use the opener's remote type.
       if (!preferredRemoteType && openerBrowser) {
@@ -2615,25 +2564,23 @@ window._gBrowser = {
 
         if (lazyBrowserURI) {
           // Lazy browser must be explicitly registered so tab will appear as
           // a switch-to-tab candidate in autocomplete.
           this.UrlbarProviderOpenTabs.registerOpenTab(lazyBrowserURI.spec,
                                                       userContextId);
           b.registeredOpenURI = lazyBrowserURI;
         }
-        if (!isForFirstWindowRestore) {
-          SessionStore.setTabState(t, {
-            entries: [{
-              url: lazyBrowserURI ? lazyBrowserURI.spec : "about:blank",
-              title: lazyTabTitle,
-              triggeringPrincipal_base64: E10SUtils.serializePrincipal(triggeringPrincipal),
-            }],
-          });
-        }
+        SessionStore.setTabState(t, {
+          entries: [{
+            url: lazyBrowserURI ? lazyBrowserURI.spec : "about:blank",
+            title: lazyTabTitle,
+            triggeringPrincipal_base64: E10SUtils.serializePrincipal(triggeringPrincipal),
+          }],
+        });
       } else {
         this._insertBrowser(t, true);
       }
     } catch (e) {
       Cu.reportError("Failed to create tab");
       Cu.reportError(e);
       t.remove();
       if (t.linkedBrowser) {
@@ -2661,19 +2608,17 @@ window._gBrowser = {
       if (!aURIObject ||
           (doGetProtocolFlags(aURIObject) & URI_INHERITS_SECURITY_CONTEXT)) {
         b.createAboutBlankContentViewer(originPrincipal);
       }
     }
 
     // If we didn't swap docShells with a preloaded browser
     // then let's just continue loading the page normally.
-    if (!usingPreloadedContent &&
-        !createLazyBrowser &&
-        (!uriIsAboutBlank || !allowInheritPrincipal)) {
+    if (!usingPreloadedContent && (!uriIsAboutBlank || !allowInheritPrincipal)) {
       // pretend the user typed this so it'll be available till
       // the document successfully loads
       if (aURI && !gInitialPages.includes(aURI)) {
         b.userTypedValue = aURI;
       }
 
       let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       if (allowThirdPartyFixup) {
@@ -2716,17 +2661,17 @@ window._gBrowser = {
       requestAnimationFrame(function() {
         // kick the animation off
         t.setAttribute("fadein", "true");
       });
     }
 
     // Additionally send pinned tab events
     if (pinned) {
-      this._notifyPinnedStatus(t, isForFirstWindowRestore);
+      this._notifyPinnedStatus(t);
     }
 
     return t;
   },
 
   moveTabsToStart(contextTab) {
     let tabs = contextTab.multiselected ?
       this.selectedTabs :
@@ -3214,20 +3159,18 @@ window._gBrowser = {
     if (aTab.hidden)
       this.tabContainer._updateHiddenTabsStatus();
 
     // ... and fix up the _tPos properties immediately.
     for (let i = aTab._tPos; i < this.tabs.length; i++)
       this.tabs[i]._tPos = i;
 
     if (!this._windowIsClosing) {
-      if (wasPinned) {
+      if (wasPinned)
         this.tabContainer._positionPinnedTabs();
-        this._maybeUpdateNumPinnedTabsPref();
-      }
 
       // update tab close buttons state
       this.tabContainer._updateCloseButtons();
 
       setTimeout(function(tabs) {
         tabs._lastTabClosedByMouse = false;
       }, 0, this.tabContainer);
     }
@@ -3890,16 +3833,17 @@ window._gBrowser = {
     let createLazyBrowser = !aTab.linkedPanel;
     let params = {
       eventDetail: { adoptedTab: aTab },
       preferredRemoteType: linkedBrowser.remoteType,
       sameProcessAsFrameLoader: linkedBrowser.frameLoader,
       skipAnimation: true,
       index: aIndex,
       createLazyBrowser,
+      allowInheritPrincipal: createLazyBrowser,
     };
 
     let numPinned = this._numPinnedTabs;
     if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
       params.pinned = true;
     }
 
     if (aTab.hasAttribute("usercontextid")) {
@@ -5066,17 +5010,16 @@ class TabProgressListener {
           // remoteness or is a new tab/window).
           this.mBrowser.urlbarChangeTracker.startedLoad();
         }
         delete this.mBrowser.initialPageLoadedFromUserAction;
         // If the browser is loading it must not be crashed anymore
         this.mTab.removeAttribute("crashed");
       }
 
-      this.mTab.removeAttribute("preopened");
       if (this._shouldShowProgress(aRequest)) {
         if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) &&
             aWebProgress && aWebProgress.isTopLevel) {
           this.mTab.setAttribute("busy", "true");
           gBrowser._tabAttrModified(this.mTab, ["busy"]);
           this.mTab._notselectedsinceload = !this.mTab.selected;
           gBrowser.syncThrobberAnimations(this.mTab);
         }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1932,20 +1932,20 @@
                   anonid="tab-loading-burst"
                   class="tab-loading-burst"/>
         <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
                   class="tab-content" align="center">
           <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
                     anonid="tab-throbber"
                     class="tab-throbber"
                     layer="true"/>
-          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon,preopened"
+          <xul:hbox xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected,pendingicon"
                     anonid="tab-icon-pending"
                     class="tab-icon-pending"/>
-          <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing,preopened"
+          <xul:image xbl:inherits="src=image,triggeringprincipal=iconloadingprincipal,requestcontextid,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
                      anonid="tab-icon-image"
                      class="tab-icon-image"
                      validate="never"
                      role="presentation"/>
           <xul:image xbl:inherits="sharing,selected=visuallyselected,pinned"
                      anonid="sharing-icon"
                      class="tab-sharing-icon-overlay"
                      role="presentation"/>
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -244,18 +244,18 @@ var SessionStore = {
   setBrowserState: function ss_setBrowserState(aState) {
     SessionStoreInternal.setBrowserState(aState);
   },
 
   getWindowState: function ss_getWindowState(aWindow) {
     return SessionStoreInternal.getWindowState(aWindow);
   },
 
-  setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite, aFirstWindow) {
-    SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite, aFirstWindow);
+  setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
+    SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
   },
 
   getTabState: function ss_getTabState(aTab) {
     return SessionStoreInternal.getTabState(aTab);
   },
 
   setTabState: function ss_setTabState(aTab, aState) {
     SessionStoreInternal.setTabState(aTab, aState);
@@ -726,17 +726,16 @@ var SessionStoreInternal = {
 
     this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
 
     this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
     this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
 
     this._restore_on_demand = this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
-    this._restore_pinned_tabs_on_demand = this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
     this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
 
     gResistFingerprintingEnabled = Services.prefs.getBoolPref("privacy.resistFingerprinting");
     Services.prefs.addObserver("privacy.resistFingerprinting", this);
   },
 
   /**
    * Called on application shutdown, after notifications:
@@ -2495,25 +2494,22 @@ var SessionStoreInternal = {
     if (DyingWindowCache.has(aWindow)) {
       let data = DyingWindowCache.get(aWindow);
       return JSON.stringify({ windows: [data] });
     }
 
     throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
   },
 
-  setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite, aFirstWindow) {
+  setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
     if (!aWindow.__SSi) {
       throw Components.Exception("Window is not tracked", Cr.NS_ERROR_INVALID_ARG);
     }
 
-    this.restoreWindows(aWindow, aState, {
-      overwriteTabs: aOverwrite,
-      firstWindow: aFirstWindow,
-    });
+    this.restoreWindows(aWindow, aState, {overwriteTabs: aOverwrite});
 
     // Notify of changes to closed objects.
     this._notifyOfClosedObjectsChange();
   },
 
   getTabState: function ssi_getTabState(aTab) {
     if (!aTab || !aTab.ownerGlobal) {
       throw Components.Exception("Need a valid tab", Cr.NS_ERROR_INVALID_ARG);
@@ -3202,17 +3198,16 @@ var SessionStoreInternal = {
     } catch (e) {}
 
     // Start the throbber to pretend we're doing something while actually
     // waiting for data from the frame script. This throbber is disabled
     // if the URI is a local about: URI.
     if (!uriObj || (uriObj && !window.gBrowser.isLocalAboutURI(uriObj))) {
       tab.setAttribute("busy", "true");
     }
-    tab.removeAttribute("preopened");
 
     // Hack to ensure that the about:home, about:newtab, and about:welcome
     // favicon is loaded instantaneously, to avoid flickering and improve
     // perceived performance.
     window.gBrowser.setDefaultIcon(tab, uriObj);
 
     TAB_STATE_FOR_BROWSER.set(tab.linkedBrowser, TAB_STATE_WILL_RESTORE);
 
@@ -3596,46 +3591,16 @@ var SessionStoreInternal = {
       if (!winData || !winData.tabs || !winData.tabs[0])
         continue;
       this._openWindowWithState({ windows: [winData] });
     }
     return Promise.all([...WINDOW_SHOWING_PROMISES.values()].map(deferred => deferred.promise));
   },
 
   /**
-   * Handles the pinning / unpinning of a selected tab restored with
-   * restoreWindow.
-   *
-   * @param aWindow
-   *        Window reference to the window used for restoration
-   * @param aWinData
-   *        The window data we're restoring
-   * @param aRestoreIndex
-   *        The index of the tab data we're currently restoring
-   * @returns the selected tab
-   */
-  _updateRestoredSelectedTabPinnedState(aWindow, aWinData, aRestoreIndex) {
-    let tabbrowser = aWindow.gBrowser;
-    let tabData = aWinData.tabs[aRestoreIndex];
-    let tab = tabbrowser.selectedTab;
-    let needsUnpin = tab.pinned && !tabData.pinned;
-    let needsPin = !tab.pinned && tabData.pinned;
-    if (needsUnpin) {
-      tabbrowser.unpinTab(tab);
-    } else if (needsPin && tab == tabbrowser.tabs[aRestoreIndex]) {
-      tabbrowser.pinTab(tab);
-    } else if (needsPin) {
-      tabbrowser.removeTab(tabbrowser.tabs[aRestoreIndex]);
-      tabbrowser.pinTab(tab);
-      tabbrowser.moveTabTo(tab, aRestoreIndex);
-    }
-    return tab;
-  },
-
-  /**
    * restore features to a single window
    * @param aWindow
    *        Window reference to the window to use for restoration
    * @param winData
    *        JS object
    * @param aOptions
    *        {overwriteTabs: true} to overwrite existing tabs w/ new ones
    *        {firstWindow: true} if this is the first non-private window we're
@@ -3680,29 +3645,26 @@ var SessionStoreInternal = {
 
     // disable smooth scrolling while adding, moving, removing and selecting tabs
     let arrowScrollbox = tabbrowser.tabContainer.arrowScrollbox;
     let smoothScroll = arrowScrollbox.smoothScroll;
     arrowScrollbox.smoothScroll = false;
 
     // We need to keep track of the initially open tabs so that they
     // can be moved to the end of the restored tabs.
-    let initialTabs = [];
+    let initialTabs;
     if (!overwriteTabs && firstWindow) {
       initialTabs = Array.slice(tabbrowser.tabs);
     }
 
     // Get rid of tabs that aren't needed anymore.
     if (overwriteTabs) {
       for (let i = tabbrowser.browsers.length - 1; i >= 0; i--) {
-        if (!tabbrowser.tabs[i].selected &&
-            !tabbrowser.tabs[i].hasAttribute("preopened")) {
+        if (!tabbrowser.tabs[i].selected) {
           tabbrowser.removeTab(tabbrowser.tabs[i]);
-        } else if (tabbrowser.tabs[i].hasAttribute("preopened")) {
-          initialTabs.push(tabbrowser.tabs[i]);
         }
       }
     }
 
     let restoreTabsLazily = this._prefBranch.getBoolPref("sessionstore.restore_tabs_lazily") && this._restore_on_demand;
 
     for (var t = 0; t < newTabCount; t++) {
       let tabData = winData.tabs[t];
@@ -3710,29 +3672,24 @@ var SessionStoreInternal = {
       let userContextId = tabData.userContextId;
       let select = t == selectTab - 1;
       let tab;
 
       // Re-use existing selected tab if possible to avoid the overhead of
       // selecting a new tab.
       if (select &&
           tabbrowser.selectedTab.userContextId == userContextId) {
-        tab = this._updateRestoredSelectedTabPinnedState(aWindow, winData, t);
-
+        tab = tabbrowser.selectedTab;
+        if (!tabData.pinned) {
+          tabbrowser.unpinTab(tab);
+        }
         tabbrowser.moveTabToEnd();
         if (aWindow.gMultiProcessBrowser && !tab.linkedBrowser.isRemoteBrowser) {
           tabbrowser.updateBrowserRemoteness(tab.linkedBrowser, true);
         }
-      } else if (tabData.pinned &&
-          tabbrowser.tabs[t] &&
-          tabbrowser.tabs[t].pinned &&
-          !tabbrowser.tabs[t].linkedPanel &&
-          tabbrowser.tabs[t].userContextId == userContextId) {
-        tab = tabbrowser.tabs[t];
-        tabbrowser.activatePreopenedPinnedTab(tab);
       }
 
       // Add a new tab if needed.
       if (!tab) {
         let createLazyBrowser = restoreTabsLazily && !select && !tabData.pinned;
 
         let url = "about:blank";
         if (createLazyBrowser && tabData.entries && tabData.entries.length) {
@@ -3771,24 +3728,21 @@ var SessionStoreInternal = {
       }
 
       if (tabData.pinned) {
         tabbrowser.pinTab(tab);
       }
     }
 
     // Move the originally open tabs to the end.
-    let endPosition = tabbrowser.tabs.length - 1;
-    for (let tab of initialTabs) {
-      if (tab.hasAttribute("preopened") &&
-          !tab.linkedPanel) {
-        tabbrowser.removeTab(tab);
-      } else if (!tab.hasAttribute("preopened")) {
-        tabbrowser.unpinTab(tab);
-        tabbrowser.moveTabTo(tab, endPosition);
+    if (initialTabs) {
+      let endPosition = tabbrowser.tabs.length - 1;
+      for (let i = 0; i < initialTabs.length; i++) {
+        tabbrowser.unpinTab(initialTabs[i]);
+        tabbrowser.moveTabTo(initialTabs[i], endPosition);
       }
     }
 
     // We want to correlate the window with data from the last session, so
     // assign another id if we have one. Otherwise clear so we don't do
     // anything with it.
     delete aWindow.__SS_lastSessionWindowID;
     if (winData.__lastSessionWindowID)
@@ -4039,19 +3993,16 @@ var SessionStoreInternal = {
     if (selectedIndex > -1) {
       this.restoreTab(tabbrowser.selectedTab, aTabData[selectedIndex]);
     }
 
     // Restore all tabs.
     for (let t = 0; t < aTabs.length; t++) {
       if (t != selectedIndex) {
         this.restoreTab(aTabs[t], aTabData[t]);
-        if (this._restore_pinned_tabs_on_demand) {
-          aTabs[t].removeAttribute("preopened");
-        }
       }
     }
   },
 
   // Restores the given tab state for a given tab.
   restoreTab(tab, tabData, options = {}) {
     let browser = tab.linkedBrowser;
 
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -108,17 +108,16 @@ support-files = file_formdata_password.h
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_global_store.js]
 [browser_history_persist.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
 [browser_old_favicon.js]
 [browser_page_title.js]
 [browser_pending_tabs.js]
-[browser_preopened_apptabs.js]
 [browser_privatetabs.js]
 [browser_purge_shistory.js]
 skip-if = e10s # Bug 1271024
 [browser_replace_load.js]
 [browser_restore_redirect.js]
 [browser_restore_cookies_noOriginAttributes.js]
 [browser_scrollPositions.js]
 [browser_scrollPositionsReaderMode.js]
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_preopened_apptabs.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
-const PREF_NUM_PINNED_TABS = "browser.tabs.firstWindowRestore.numPinnedTabs";
-
-function muffleMainWindowType() {
-  let oldWinType = document.documentElement.getAttribute("windowtype");
-  // Check if we've already done this to allow calling multiple times:
-  if (oldWinType != "navigator:testrunner") {
-    // Make the main test window not count as a browser window any longer
-    document.documentElement.setAttribute("windowtype", "navigator:testrunner");
-
-    registerCleanupFunction(() => {
-      document.documentElement.setAttribute("windowtype", oldWinType);
-    });
-  }
-}
-
-registerCleanupFunction(function() {
-  Services.prefs.clearUserPref(PREF_NUM_PINNED_TABS);
-});
-
-let testCases = [
-  {numPinnedPref: 5, selected: 3, overrideTabs: false},
-  {numPinnedPref: 5, selected: 3, overrideTabs: true},
-  {numPinnedPref: 5, selected: 1, overrideTabs: false},
-  {numPinnedPref: 5, selected: 1, overrideTabs: true},
-  {numPinnedPref: 3, selected: 3, overrideTabs: false},
-  {numPinnedPref: 3, selected: 3, overrideTabs: true},
-  {numPinnedPref: 3, selected: 1, overrideTabs: false},
-  {numPinnedPref: 3, selected: 1, overrideTabs: true},
-  {numPinnedPref: 1, selected: 3, overrideTabs: false},
-  {numPinnedPref: 1, selected: 3, overrideTabs: true},
-  {numPinnedPref: 1, selected: 1, overrideTabs: false},
-  {numPinnedPref: 1, selected: 1, overrideTabs: true},
-  {numPinnedPref: 0, selected: 3, overrideTabs: false},
-  {numPinnedPref: 0, selected: 3, overrideTabs: true},
-  {numPinnedPref: 0, selected: 1, overrideTabs: false},
-  {numPinnedPref: 0, selected: 1, overrideTabs: true},
-];
-
-for (let {numPinnedPref, selected, overrideTabs} of testCases) {
-  add_task(async function testPrefSynced() {
-    Services.prefs.setIntPref(PREF_NUM_PINNED_TABS, numPinnedPref);
-
-    let state = { windows: [{ tabs: [
-      { entries: [{ url: "http://example.org/#1", triggeringPrincipal_base64 }], pinned: true, userContextId: 0 },
-      { entries: [{ url: "http://example.org/#2", triggeringPrincipal_base64 }], pinned: true, userContextId: 0 },
-      { entries: [{ url: "http://example.org/#3", triggeringPrincipal_base64 }], pinned: true, userContextId: 0 },
-    ], selected }] };
-
-    muffleMainWindowType();
-    let win = await BrowserTestUtils.openNewBrowserWindow();
-    Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, numPinnedPref);
-    Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length, 1);
-    await setWindowState(win, state, overrideTabs, true);
-    Assert.equal([...win.gBrowser.tabs].filter(t => t.pinned).length, 3);
-    Assert.equal([...win.gBrowser.tabs].filter(t => !!t.linkedPanel).length,
-                 overrideTabs ? 3 : 4);
-    await BrowserTestUtils.closeWindow(win);
-  });
-}
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -185,19 +185,18 @@ function promiseWindowRestored(win) {
   return new Promise(resolve => win.addEventListener("SSWindowRestored", resolve, {once: true}));
 }
 
 async function setBrowserState(state, win = window) {
   ss.setBrowserState(typeof state != "string" ? JSON.stringify(state) : state);
   await promiseWindowRestored(win);
 }
 
-async function setWindowState(win, state, overwrite = false, firstWindow = false) {
-  ss.setWindowState(win, typeof state != "string" ? JSON.stringify(state) : state,
-                    overwrite, firstWindow);
+async function setWindowState(win, state, overwrite = false) {
+  ss.setWindowState(win, typeof state != "string" ? JSON.stringify(state) : state, overwrite);
   await promiseWindowRestored(win);
 }
 
 /**
  * Wait for a content -> chrome message.
  */
 function promiseContentMessage(browser, name) {
   let mm = browser.messageManager;
--- a/browser/modules/BrowserWindowTracker.jsm
+++ b/browser/modules/BrowserWindowTracker.jsm
@@ -91,21 +91,16 @@ function _trackWindowOrder(window) {
 }
 
 function _untrackWindowOrder(window) {
   let idx = _trackedWindows.indexOf(window);
   if (idx >= 0)
     _trackedWindows.splice(idx, 1);
 }
 
-function _trackPinnedTabs(window) {
-  Services.prefs.setIntPref("browser.tabs.firstWindowRestore.numPinnedTabs",
-                            window.gBrowser._numPinnedTabs);
-}
-
 // Methods that impact a window. Put into single object for organization.
 var WindowHelper = {
   addWindow(window) {
     // Add event listeners
     TAB_EVENTS.forEach(function(event) {
       window.gBrowser.tabContainer.addEventListener(event, _handleEvent);
     });
     WINDOW_EVENTS.forEach(function(event) {
@@ -138,17 +133,16 @@ var WindowHelper = {
 
   onActivate(window) {
     // If this window was the last focused window, we don't need to do anything
     if (window == _trackedWindows[0])
       return;
 
     _untrackWindowOrder(window);
     _trackWindowOrder(window);
-    _trackPinnedTabs(window);
 
     _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser);
   },
 };
 
 this.BrowserWindowTracker = {
   /**
    * Get the most recent browser window.
--- a/caps/ContentPrincipal.cpp
+++ b/caps/ContentPrincipal.cpp
@@ -147,18 +147,18 @@ nsresult ContentPrincipal::GenerateOrigi
   bool isBehaved;
   if ((NS_SUCCEEDED(origin->SchemeIs("about", &isBehaved)) && isBehaved) ||
       (NS_SUCCEEDED(origin->SchemeIs("moz-safe-about", &isBehaved)) &&
        isBehaved &&
        // We generally consider two about:foo origins to be same-origin, but
        // about:blank is special since it can be generated from different
        // sources. We check for moz-safe-about:blank since origin is an
        // innermost URI.
-       !origin->GetSpecOrDefault().EqualsLiteral("moz-safe-about:blank")) ||
-      (NS_SUCCEEDED(origin->SchemeIs("indexeddb", &isBehaved)) && isBehaved)) {
+       !StringBeginsWith(origin->GetSpecOrDefault(),
+                         NS_LITERAL_CSTRING("moz-safe-about:blank")))) {
     rv = origin->GetAsciiSpec(aOriginNoSuffix);
     NS_ENSURE_SUCCESS(rv, rv);
 
     int32_t pos = aOriginNoSuffix.FindChar('?');
     int32_t hashPos = aOriginNoSuffix.FindChar('#');
 
     if (hashPos != kNotFound && (pos == kNotFound || hashPos < pos)) {
       pos = hashPos;
@@ -405,16 +405,23 @@ static nsresult GetSpecialBaseDomain(con
     return rv;
   }
 
   if (hasNoRelativeFlag) {
     *aHandled = true;
     return aCodebase->GetSpec(aBaseDomain);
   }
 
+  bool isBehaved;
+  if (NS_SUCCEEDED(aCodebase->SchemeIs("indexeddb", &isBehaved)) &&
+      isBehaved) {
+    *aHandled = true;
+    return aCodebase->GetSpec(aBaseDomain);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain) {
   // Handle some special URIs first.
   bool handled;
   nsresult rv = GetSpecialBaseDomain(mCodebase, &handled, aBaseDomain);
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -119,17 +119,17 @@ bool nsJSPrincipals::ReadPrincipals(JSCo
     return false;
   }
 
   return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals);
 }
 
 static bool ReadPrincipalInfo(
     JSStructuredCloneReader* aReader, OriginAttributes& aAttrs,
-    nsACString& aSpec, nsACString& aOriginNoSuffix,
+    nsACString& aSpec, nsACString& aOriginNoSuffix, nsACString& aBaseDomain,
     nsTArray<ContentSecurityPolicy>* aPolicies = nullptr) {
   uint32_t suffixLength, specLength;
   if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
     return false;
   }
 
   nsAutoCString suffix;
   if (!suffix.SetLength(suffixLength, fallible)) {
@@ -188,28 +188,51 @@ static bool ReadPrincipalInfo(
     }
 
     if (aPolicies) {
       aPolicies->AppendElement(ContentSecurityPolicy(
           NS_ConvertUTF8toUTF16(policyStr), reportOnly, deliveredViaMetaTag));
     }
   }
 
+  uint32_t baseDomainIsVoid, baseDomainLength;
+  if (!JS_ReadUint32Pair(aReader, &baseDomainIsVoid, &baseDomainLength)) {
+    return false;
+  }
+
+  MOZ_ASSERT(baseDomainIsVoid == 0 || baseDomainIsVoid == 1);
+
+  if (baseDomainIsVoid) {
+    MOZ_ASSERT(baseDomainLength == 0);
+
+    aBaseDomain.SetIsVoid(true);
+    return true;
+  }
+
+  if (!aBaseDomain.SetLength(baseDomainLength, fallible)) {
+    return false;
+  }
+
+  if (!JS_ReadBytes(aReader, aBaseDomain.BeginWriting(), baseDomainLength)) {
+    return false;
+  }
+
   return true;
 }
 
 static bool ReadPrincipalInfo(JSStructuredCloneReader* aReader, uint32_t aTag,
                               PrincipalInfo& aInfo) {
   if (aTag == SCTAG_DOM_SYSTEM_PRINCIPAL) {
     aInfo = SystemPrincipalInfo();
   } else if (aTag == SCTAG_DOM_NULL_PRINCIPAL) {
     OriginAttributes attrs;
     nsAutoCString spec;
     nsAutoCString originNoSuffix;
-    if (!ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix)) {
+    nsAutoCString baseDomain;
+    if (!ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, baseDomain)) {
       return false;
     }
     aInfo = NullPrincipalInfo(attrs, spec);
   } else if (aTag == SCTAG_DOM_EXPANDED_PRINCIPAL) {
     uint32_t length, unused;
     if (!JS_ReadUint32Pair(aReader, &length, &unused)) {
       return false;
     }
@@ -229,32 +252,34 @@ static bool ReadPrincipalInfo(JSStructur
       expanded.allowlist().AppendElement(sub);
     }
 
     aInfo = expanded;
   } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
     OriginAttributes attrs;
     nsAutoCString spec;
     nsAutoCString originNoSuffix;
+    nsAutoCString baseDomain;
     nsTArray<ContentSecurityPolicy> policies;
-    if (!ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, &policies)) {
+    if (!ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, baseDomain,
+                           &policies)) {
       return false;
     }
 
 #ifdef FUZZING
     if (originNoSuffix.IsEmpty()) {
       return false;
     }
 #endif
 
     MOZ_DIAGNOSTIC_ASSERT(!originNoSuffix.IsEmpty());
 
     // XXX: Do we care about mDomain for structured clone?
     aInfo = ContentPrincipalInfo(attrs, originNoSuffix, spec, Nothing(),
-                                 std::move(policies));
+                                 std::move(policies), baseDomain);
   } else {
 #ifdef FUZZING
     return false;
 #else
     MOZ_CRASH("unexpected principal structured clone tag");
 #endif
   }
 
@@ -290,16 +315,17 @@ bool nsJSPrincipals::ReadKnownPrincipalT
 
   *aOutPrincipals = get(prin.forget().take());
   return true;
 }
 
 static bool WritePrincipalInfo(
     JSStructuredCloneWriter* aWriter, const OriginAttributes& aAttrs,
     const nsCString& aSpec, const nsCString& aOriginNoSuffix,
+    const nsCString& aBaseDomain,
     const nsTArray<ContentSecurityPolicy>* aPolicies = nullptr) {
   nsAutoCString suffix;
   aAttrs.CreateSuffix(suffix);
   size_t policyCount = aPolicies ? aPolicies->Length() : 0;
 
   if (!(JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) &&
         JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
         JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()) &&
@@ -317,26 +343,31 @@ static bool WritePrincipalInfo(
         ((*aPolicies)[i].deliveredViaMetaTagFlag() ? 2 : 0);
     if (!(JS_WriteUint32Pair(aWriter, policy.Length(), reportAndMeta) &&
           JS_WriteBytes(aWriter, PromiseFlatCString(policy).get(),
                         policy.Length()))) {
       return false;
     }
   }
 
-  return true;
+  if (aBaseDomain.IsVoid()) {
+    return JS_WriteUint32Pair(aWriter, 1, 0);
+  }
+
+  return JS_WriteUint32Pair(aWriter, 0, aBaseDomain.Length()) &&
+         JS_WriteBytes(aWriter, aBaseDomain.get(), aBaseDomain.Length());
 }
 
 static bool WritePrincipalInfo(JSStructuredCloneWriter* aWriter,
                                const PrincipalInfo& aInfo) {
   if (aInfo.type() == PrincipalInfo::TNullPrincipalInfo) {
     const NullPrincipalInfo& nullInfo = aInfo;
     return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0) &&
            WritePrincipalInfo(aWriter, nullInfo.attrs(), nullInfo.spec(),
-                              EmptyCString());
+                              EmptyCString(), EmptyCString());
   }
   if (aInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     return JS_WriteUint32Pair(aWriter, SCTAG_DOM_SYSTEM_PRINCIPAL, 0);
   }
   if (aInfo.type() == PrincipalInfo::TExpandedPrincipalInfo) {
     const ExpandedPrincipalInfo& expanded = aInfo;
     if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_EXPANDED_PRINCIPAL, 0) ||
         !JS_WriteUint32Pair(aWriter, expanded.allowlist().Length(), 0)) {
@@ -350,17 +381,17 @@ static bool WritePrincipalInfo(JSStructu
     }
     return true;
   }
 
   MOZ_ASSERT(aInfo.type() == PrincipalInfo::TContentPrincipalInfo);
   const ContentPrincipalInfo& cInfo = aInfo;
   return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
          WritePrincipalInfo(aWriter, cInfo.attrs(), cInfo.spec(),
-                            cInfo.originNoSuffix(),
+                            cInfo.originNoSuffix(), cInfo.baseDomain(),
                             &(cInfo.securityPolicies()));
 }
 
 bool nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter) {
   PrincipalInfo info;
   if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) {
     xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
     return false;
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -652,16 +652,17 @@ support-files =
   examples/script-switching-02.js
   examples/script-switching-01.js
   examples/times2.js
   examples/doc-windowless-workers.html
   examples/doc-windowless-workers-early-breakpoint.html
   examples/simple-worker.js
   examples/doc-event-handler.html
   examples/doc-eval-throw.html
+  examples/doc-sourceURL-breakpoint.html
 
 [browser_dbg-asm.js]
 [browser_dbg-async-stepping.js]
 [browser_dbg-sourcemapped-breakpoint-console.js]
 skip-if = (os == "win" && ccov) # Bug 1453549
 [browser_dbg-xhr-breakpoints.js]
 [browser_dbg-xhr-run-to-completion.js]
 [browser_dbg-scroll-run-to-completion.js]
@@ -769,8 +770,9 @@ skip-if = os == "win"
 [browser_dbg-react-app.js]
 skip-if = os == "win"
 [browser_dbg-wasm-sourcemaps.js]
 skip-if = true
 [browser_dbg-windowless-workers.js]
 [browser_dbg-windowless-workers-early-breakpoint.js]
 [browser_dbg-event-handler.js]
 [browser_dbg-eval-throw.js]
+[browser_dbg-sourceURL-breakpoint.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-sourceURL-breakpoint.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that breakpoints are hit in eval'ed sources with a sourceURL property.
+add_task(async function() {
+  const dbg = await initDebugger("doc-sourceURL-breakpoint.html", "my-foo.js");
+  await selectSource(dbg, "my-foo.js");
+  await addBreakpoint(dbg, "my-foo.js", 2);
+
+  invokeInTab("foo");
+  await waitForPaused(dbg);
+
+  ok(true, "paused at breakpoint");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/new/test/mochitest/examples/doc-sourceURL-breakpoint.html
@@ -0,0 +1,9 @@
+<body>
+<script>
+var script = `function foo() {
+               console.log('called foo');
+             }
+             //# sourceURL=my-foo.js`;
+eval(script);
+</script>
+</body>
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -751,36 +751,38 @@ function getFirstBreakpointColumn(dbg, {
  * @memberof mochitest/actions
  * @param {Object} dbg
  * @param {String} source
  * @param {Number} line
  * @param {Number} col
  * @return {Promise}
  * @static
  */
-async function addBreakpoint(dbg, source, line, column) {
+async function addBreakpoint(dbg, source, line, column, options) {
   source = findSource(dbg, source);
   const sourceId = source.id;
   column = column || getFirstBreakpointColumn(dbg, {line, sourceId: source.id});
   const bpCount = dbg.selectors.getBreakpointCount(dbg.getState());
-  dbg.actions.addBreakpoint({ sourceId, line, column });
+  dbg.actions.addBreakpoint({ sourceId, line, column }, options);
   await waitForDispatch(dbg, "ADD_BREAKPOINT");
   is(dbg.selectors.getBreakpointCount(dbg.getState()), bpCount + 1, "a new breakpoint was created");
 }
 
 function disableBreakpoint(dbg, source, line, column) {
+  column = column || getFirstBreakpointColumn(dbg, {line, sourceId: source.id});
   const location = { sourceId: source.id, sourceUrl: source.url, line, column };
   const bp = dbg.selectors.getBreakpointForLocation(dbg.getState(), location);
   dbg.actions.disableBreakpoint(bp);
   return waitForDispatch(dbg, "DISABLE_BREAKPOINT");
 }
 
 function setBreakpointOptions(dbg, source, line, column, options) {
   source = findSource(dbg, source);
   const sourceId = source.id;
+  column = column || getFirstBreakpointColumn(dbg, {line, sourceId});
   dbg.actions.setBreakpointOptions({ sourceId, line, column }, options);
   return waitForDispatch(dbg, "SET_BREAKPOINT_OPTIONS");
 }
 
 function findBreakpoint(dbg, url, line) {
   const {
     selectors: { getBreakpoint, getBreakpointsList },
     getState
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -231,11 +231,12 @@ subsuite = clipboard
 [browser_markup_textcontent_edit_01.js]
 [browser_markup_textcontent_edit_02.js]
 [browser_markup_toggle_01.js]
 [browser_markup_toggle_02.js]
 [browser_markup_toggle_03.js]
 [browser_markup_toggle_04.js]
 [browser_markup_toggle_closing_tag_line.js]
 [browser_markup_update-on-navigtion.js]
+[browser_markup_view-source.js]
 [browser_markup_void_elements_html.js]
 [browser_markup_void_elements_xhtml.js]
 [browser_markup_whitespace.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_view-source.js
@@ -0,0 +1,63 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const DOCUMENT_SRC = `
+<body>
+<button id="foo">Button</button>
+<script>
+var script = \`
+  function foo() {
+    console.log('handler');
+  }
+\`;
+eval(script);
+
+var button = document.getElementById("foo");
+button.addEventListener("click", foo, false);
+</script>
+</body>`;
+
+const TEST_URI = "data:text/html;charset=utf-8," + DOCUMENT_SRC;
+
+add_task(async function() {
+  // Test that event handler links go to the right debugger source when it
+  // came from an eval().
+  const { inspector, toolbox } = await openInspectorForURL(TEST_URI);
+
+  const target = await TargetFactory.forTab(gBrowser.selectedTab);
+
+  const nodeFront = await getNodeFront("#foo", inspector);
+  const container = getContainerForNodeFront(nodeFront, inspector);
+
+  const evHolder = container.elt.querySelector(
+    ".inspector-badge.interactive[data-event]");
+
+  evHolder.scrollIntoView();
+  EventUtils.synthesizeMouseAtCenter(evHolder, {},
+    inspector.markup.doc.defaultView);
+
+  const tooltip = inspector.markup.eventDetailsTooltip;
+  await tooltip.once("shown");
+
+  const debuggerIcon = tooltip.panel.querySelector(".event-tooltip-debugger-icon");
+  EventUtils.synthesizeMouse(debuggerIcon, 2, 2, {}, debuggerIcon.ownerGlobal);
+
+  await gDevTools.showToolbox(target, "jsdebugger");
+  const dbg = toolbox.getPanel("jsdebugger");
+
+  let source;
+  await BrowserTestUtils.waitForCondition(() => {
+    source = dbg._selectors.getSelectedSource(dbg._getState());
+    return !!source;
+  }, "loaded source", 100, 20);
+
+  is(
+    source.url,
+    null,
+    "expected no url for eval source"
+  );
+});
--- a/devtools/client/shared/components/Frame.js
+++ b/devtools/client/shared/components/Frame.js
@@ -135,18 +135,19 @@ class Frame extends Component {
     const unicodeHost  = host ? getUnicodeHostname(host) : "";
 
     // Reparse the URL to determine if we should link this; `getSourceNames`
     // has already cached this indirectly. We don't want to attempt to
     // link to "self-hosted" and "(unknown)". However, we do want to link
     // to Scratchpad URIs.
     // Source mapped sources might not necessary linkable, but they
     // are still valid in the debugger.
+    // If we have a source ID then we can show the source in the debugger.
     const isLinkable = !!(isScratchpadScheme(source) || parseURL(source))
-      || isSourceMapped;
+      || isSourceMapped || sourceId;
     const elements = [];
     const sourceElements = [];
     let sourceEl;
     let tooltip = unicodeLong;
 
     // Exclude all falsy values, including `0`, as line numbers start with 1.
     if (line) {
       tooltip += `:${line}`;
--- a/devtools/client/shared/view-source.js
+++ b/devtools/client/shared/view-source.js
@@ -47,17 +47,18 @@ exports.viewSourceInStyleEditor = async 
  * @param {string} sourceID
  * @param {string} [reason=unknown]
  *
  * @return {Promise<boolean>}
  */
 exports.viewSourceInDebugger = async function(toolbox, sourceURL, sourceLine, sourceId,
                                               reason = "unknown") {
   const dbg = await toolbox.loadTool("jsdebugger");
-  const source = dbg.getSourceByURL(sourceURL) || dbg.getSourceByActorId(sourceId);
+  const source =
+    sourceId ? dbg.getSourceByActorId(sourceId) : dbg.getSourceByURL(sourceURL);
   if (source) {
     await toolbox.selectTool("jsdebugger", reason);
     dbg.selectSource(source.id, sourceLine);
     return true;
   } else if (await toolbox.sourceMapService.hasOriginalURL(sourceURL)) {
     // We have seen a source map for the URL but no source. The debugger will
     // still be able to load the source.
     await toolbox.selectTool("jsdebugger", reason);
--- a/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -179,16 +179,17 @@ EventTooltip.prototype = {
       }
 
       // Content
       const editor = new Editor(config);
       this._eventEditors.set(content, {
         editor: editor,
         handler: listener.handler,
         uri: listener.origin,
+        sourceActor: listener.sourceActor,
         dom0: listener.DOM0,
         native: listener.native,
         appended: false,
       });
 
       content.className = "event-tooltip-content-box";
       this.container.appendChild(content);
 
@@ -258,26 +259,26 @@ EventTooltip.prototype = {
       });
     }
   },
 
   _debugClicked: function(event) {
     const header = event.currentTarget;
     const content = header.nextElementSibling;
 
-    const {uri} = this._eventEditors.get(content);
+    const {sourceActor, uri} = this._eventEditors.get(content);
 
     const location = this._parseLocation(uri);
     if (location) {
       // Save a copy of toolbox as it will be set to null when we hide the tooltip.
       const toolbox = this._toolbox;
 
       this._tooltip.hide();
 
-      toolbox.viewSourceInDebugger(location.url, location.line);
+      toolbox.viewSourceInDebugger(location.url, location.line, sourceActor);
     }
   },
 
   /**
    * Parse URI and return {url, line}; or return null if it can't be parsed.
    */
   _parseLocation: function(uri) {
     if (uri && uri !== "?") {
--- a/devtools/client/webconsole/test/fixtures/stubs/pageError.js
+++ b/devtools/client/webconsole/test/fixtures/stubs/pageError.js
@@ -107,16 +107,17 @@ stubPreparedMessages.set(`SyntaxError: r
   "frame": {
     "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "sourceId": null,
     "line": 2,
     "column": 9
   },
   "groupId": null,
   "errorMessageName": "JSMSG_REDECLARED_VAR",
+  "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Redeclared_parameter?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
   "userProvidedStyles": null,
   "notes": [
     {
       "messageBody": "Previously declared at line 2, column 6",
       "frame": {
         "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
         "sourceId": null,
         "line": 2,
@@ -287,16 +288,17 @@ stubPackets.set(`ReferenceError: asdf is
   "type": "pageError",
   "from": "server1.conn0.child1/consoleActor2"
 });
 
 stubPackets.set(`SyntaxError: redeclaration of let a`, {
   "pageError": {
     "errorMessage": "SyntaxError: redeclaration of let a",
     "errorMessageName": "JSMSG_REDECLARED_VAR",
+    "exceptionDocURL": "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Redeclared_parameter?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default",
     "sourceName": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "sourceId": null,
     "lineText": "  let a, a;",
     "lineNumber": 2,
     "columnNumber": 9,
     "category": "content javascript",
     "timeStamp": 1487992945524,
     "warning": false,
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_eval_sources.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_eval_sources.js
@@ -3,21 +3,22 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/mochitest/test-eval-sources.html";
 
-async function clickFirstStackElement(hud, message) {
-  const button = message.querySelector(".collapse-button");
-  ok(button, "has button");
-
-  button.click();
+async function clickFirstStackElement(hud, message, needsExpansion) {
+  if (needsExpansion) {
+    const button = message.querySelector(".collapse-button");
+    ok(button, "has button");
+    button.click();
+  }
 
   let frame;
   await waitUntil(() => {
     frame = message.querySelector(".frame");
     return !!frame;
   });
 
   EventUtils.sendMouseEvent({ type: "mousedown" }, frame);
@@ -28,22 +29,36 @@ async function clickFirstStackElement(hu
 // Test that stack/message links in console API and error messages originating
 // from eval code go to a source in the debugger. This should work even when the
 // console is opened first.
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
   const toolbox = gDevTools.getToolbox(target);
 
-  const messageNode = await waitFor(() => findMessage(hud, "BAR"));
-  await clickFirstStackElement(hud, messageNode);
+  let messageNode = await waitFor(() => findMessage(hud, "BAR"));
+  await clickFirstStackElement(hud, messageNode, true);
 
   const dbg = toolbox.getPanel("jsdebugger");
 
   is(
     dbg._selectors.getSelectedSource(dbg._getState()).url,
     null,
     "expected source url"
   );
 
   await testOpenInDebugger(hud, toolbox, "FOO", false);
   await testOpenInDebugger(hud, toolbox, "BAR", false);
+
+  // Test that links in the API work when the eval source has a sourceURL property
+  // which is not considered to be a valid URL.
+  await testOpenInDebugger(hud, toolbox, "BAZ", false);
+
+  // Test that stacks in console.trace() calls work.
+  messageNode = await waitFor(() => findMessage(hud, "TRACE"));
+  await clickFirstStackElement(hud, messageNode, false);
+
+  is(
+    /my-foo.js/.test(dbg._selectors.getSelectedSource(dbg._getState()).url),
+    true,
+    "expected source url"
+  );
 });
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -372,21 +372,22 @@ async function checkClickOnNode(hud, too
 
   const dbg = toolbox.getPanel("jsdebugger");
 
   // Wait for the source to finish loading, if it is pending.
   await waitFor(() => {
     return !!dbg._selectors.getSelectedSource(dbg._getState());
   });
 
-  is(
-    dbg._selectors.getSelectedSource(dbg._getState()).url,
-    expectUrl ? url : null,
-    "expected source url"
-  );
+  if (expectUrl) {
+    is(
+      dbg._selectors.getSelectedSource(dbg._getState()).url, url,
+      "expected source url"
+    );
+  }
 }
 
 /**
  * Returns true if the give node is currently focused.
  */
 function hasFocus(node) {
   return node.ownerDocument.activeElement == node
     && node.ownerDocument.hasFocus();
--- a/devtools/client/webconsole/test/mochitest/test-eval-sources.html
+++ b/devtools/client/webconsole/test/mochitest/test-eval-sources.html
@@ -1,9 +1,17 @@
 <!DOCTYPE html>
 <meta charset=UTF-8>
 <script>
 /* eslint-disable */
 eval("window.foo = function() { console.log('FOO'); }");
 eval("window.bar = function() { throw new Error('BAR') };");
+eval(`window.baz = function() {
+        console.log('BAZ');
+        console.trace('TRACE');
+     }
+     //# sourceURL=my-foo.js`);
+
 foo();
+baz();
 bar();
+
 </script>
--- a/devtools/client/webreplay/mochitest/browser_dbg_rr_logpoint-02.js
+++ b/devtools/client/webreplay/mochitest/browser_dbg_rr_logpoint-02.js
@@ -13,16 +13,17 @@ add_task(async function() {
     "doc_rr_basic.html",
     { waitForRecording: true }
   );
 
   const {tab, toolbox} = dbg;
   const console = await getDebuggerSplitConsole(dbg);
   const hud = console.hud;
 
+  await selectSource(dbg, "doc_rr_basic.html");
   await addBreakpoint(dbg, "doc_rr_basic.html", 21, undefined,
                       { logValue: `"Logpoint Number " + number` });
   await addBreakpoint(dbg, "doc_rr_basic.html", 6, undefined,
                       { logValue: `"Logpoint Beginning"` });
   await addBreakpoint(dbg, "doc_rr_basic.html", 8, undefined,
                       { logValue: `"Logpoint Ending"` });
   await waitForMessageCount(hud, "Logpoint", 12);
 
--- a/devtools/server/actors/errordocs.js
+++ b/devtools/server/actors/errordocs.js
@@ -84,16 +84,17 @@ const ErrorDocs = {
   JSMSG_CANT_TRUNCATE_ARRAY: "Non_configurable_array_element",
   JSMSG_INCOMPATIBLE_PROTO: "Called_on_incompatible_type",
   JSMSG_INCOMPATIBLE_METHOD: "Called_on_incompatible_type",
   JSMSG_BAD_INSTANCEOF_RHS: "invalid_right_hand_side_instanceof_operand",
   JSMSG_EMPTY_ARRAY_REDUCE: "Reduce_of_empty_array_with_no_initial_value",
   JSMSG_NOT_ITERABLE: "is_not_iterable",
   JSMSG_PROPERTY_FAIL: "cant_access_property",
   JSMSG_PROPERTY_FAIL_EXPR: "cant_access_property",
+  JSMSG_REDECLARED_VAR: "Redeclared_parameter",
 };
 
 const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Mixed_content";
 const TRACKING_PROTECTION_LEARN_MORE = "https://developer.mozilla.org/Firefox/Privacy/Tracking_Protection";
 const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Insecure_passwords";
 const PUBLIC_KEY_PINS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/HTTP/Public_Key_Pinning";
 const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Web/HTTP/Headers/Strict-Transport-Security";
 const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Weak_Signature_Algorithm";
--- a/devtools/server/actors/inspector/event-collector.js
+++ b/devtools/server/actors/inspector/event-collector.js
@@ -703,17 +703,19 @@ class ReactEventCollector extends MainEv
     return handlerDO;
   }
 }
 
 /**
  * The exposed class responsible for gathering events.
  */
 class EventCollector {
-  constructor() {
+  constructor(targetActor) {
+    this.targetActor = targetActor;
+
     // The event collector array. Please preserve the order otherwise there will
     // be multiple failing tests.
     this.eventCollectors = [
       new ReactEventCollector(),
       new JQueryLiveEventCollector(),
       new JQueryEventCollector(),
       new DOMEventCollector(),
     ];
@@ -859,16 +861,17 @@ class EventCollector {
       const override = listener.override || {};
       const tags = listener.tags || "";
       const type = listener.type || "";
       let dom0 = false;
       let functionSource = handler.toString();
       let line = 0;
       let native = false;
       let url = "";
+      let sourceActor = "";
 
       // If the listener is an object with a 'handleEvent' method, use that.
       if (listenerDO.class === "Object" || /^XUL\w*Element$/.test(listenerDO.class)) {
         let desc;
 
         while (!desc && listenerDO) {
           desc = listenerDO.getOwnPropertyDescriptor("handleEvent");
           listenerDO = listenerDO.proto;
@@ -895,16 +898,18 @@ class EventCollector {
         if (script.source.element) {
           dom0 = script.source.element.class !== "HTMLScriptElement";
         } else {
           dom0 = false;
         }
 
         line = script.startLine;
         url = script.url;
+        const actor = this.targetActor.sources.getOrCreateSourceActor(script.source);
+        sourceActor = actor ? actor.actorID : null;
 
         // Checking for the string "[native code]" is the only way at this point
         // to check for native code. Even if this provides a false positive then
         // grabbing the source code a second time is harmless.
         if (functionSource === "[object Object]" ||
             functionSource === "[object XULElement]" ||
             functionSource.includes("[native code]")) {
           functionSource =
@@ -952,16 +957,17 @@ class EventCollector {
         handler: override.handler || functionSource.trim(),
         origin: override.origin || origin,
         tags: override.tags || tags,
         DOM0: typeof override.dom0 !== "undefined" ? override.dom0 : dom0,
         capturing: typeof override.capturing !== "undefined" ?
                           override.capturing : capturing,
         hide: typeof override.hide !== "undefined" ? override.hide : hide,
         native,
+        sourceActor,
       };
 
       // Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
       // generated dynamically from e.g. an onclick="" attribute so the script
       // doesn't actually exist.
       if (native || dom0) {
         eventObj.hide.debugger = true;
       }
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -43,17 +43,17 @@ const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20
 /**
  * Server side of the node actor.
  */
 const NodeActor = protocol.ActorClassWithSpec(nodeSpec, {
   initialize: function(walker, node) {
     protocol.Actor.prototype.initialize.call(this, null);
     this.walker = walker;
     this.rawNode = node;
-    this._eventCollector = new EventCollector();
+    this._eventCollector = new EventCollector(this.walker.targetActor);
 
     // Store the original display type and scrollable state and whether or not the node is
     // displayed to track changes when reflows occur.
     this.currentDisplayType = this.displayType;
     this.wasDisplayed = this.isDisplayed;
     this.wasScrollable = this.isScrollable;
   },
 
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -1136,20 +1136,18 @@ const ThreadActor = ActorClassWithSpec(t
       } else if (actor.destroy) {
         actor.destroy();
       }
     }
     return res ? res : {};
   },
 
   onSources: function(request) {
-    // FIXME bug 1530699 we should make sure that existing breakpoints are
-    // applied to any sources we find here.
     for (const source of this.dbg.findSources()) {
-      this.sources.createSourceActor(source);
+      this._addSource(source);
     }
 
     // No need to flush the new source packets here, as we are sending the
     // list of sources out immediately and we don't need to invoke the
     // overhead of an RDP packet for every source right now. Let the default
     // timeout flush the buffered packets.
 
     return {
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -142,40 +142,44 @@ TabSources.prototype = {
     if (!sourceActor) {
       throw new Error("getSource: could not find source actor for " +
                       (source.url || "source"));
     }
 
     return sourceActor;
   },
 
+  getOrCreateSourceActor(source) {
+    if (this.hasSourceActor(source)) {
+      return this.getSourceActor(source);
+    }
+    return this.createSourceActor(source);
+  },
+
   getSourceActorByInternalSourceId: function(id) {
     if (!this._sourcesByInternalSourceId) {
       this._sourcesByInternalSourceId = new Map();
       for (const source of this._thread.dbg.findSources()) {
         if (source.id) {
           this._sourcesByInternalSourceId.set(source.id, source);
         }
       }
     }
     const source = this._sourcesByInternalSourceId.get(id);
     if (source) {
-      if (this.hasSourceActor(source)) {
-        return this.getSourceActor(source);
-      }
-      return this.createSourceActor(source);
+      return this.getOrCreateSourceActor(source);
     }
     return null;
   },
 
   getSourceActorsByURL: function(url) {
     const rv = [];
     if (url) {
-      for (const [source, actor] of this._sourceActors) {
-        if (source.url === url) {
+      for (const [, actor] of this._sourceActors) {
+        if (actor.url === url) {
           rv.push(actor);
         }
       }
     }
     return rv;
   },
 
   getSourceActorById(actorId) {
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1696,16 +1696,22 @@ WebConsoleActor.prototype =
 
     result.sourceId = this.getActorIdForInternalSourceId(result.sourceId);
 
     delete result.wrappedJSObject;
     delete result.ID;
     delete result.innerID;
     delete result.consoleID;
 
+    if (result.stacktrace) {
+      result.stacktrace = Array.map(result.stacktrace, (frame) => {
+        return { ...frame, sourceId: this.getActorIdForInternalSourceId(frame.sourceId) };
+      });
+    }
+
     result.arguments = Array.map(message.arguments || [], (obj) => {
       const dbgObj = this.makeDebuggeeValue(obj, useObjectGlobal);
       return this.createValueGrip(dbgObj);
     });
 
     result.styles = Array.map(message.styles || [], (string) => {
       return this.createValueGrip(string);
     });
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -1143,16 +1143,21 @@ PAsmJSCacheEntryParent* AllocEntryParent
     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);
@@ -1403,16 +1408,21 @@ ChildRunnable::Run() {
 
       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;
       }
 
--- a/dom/base/ThirdPartyUtil.cpp
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -10,16 +10,17 @@
 #include "nsIChannel.h"
 #include "nsIServiceManager.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIDOMWindow.h"
 #include "nsILoadContext.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
+#include "nsReadableUtils.h"
 #include "nsThreadUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Unused.h"
 #include "nsPIDOMWindow.h"
 
 NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
@@ -28,16 +29,24 @@ NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozITh
 // MOZ_LOG=thirdPartyUtil:5
 //
 static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil");
 #undef LOG
 #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
 
 static mozilla::StaticRefPtr<ThirdPartyUtil> gService;
 
+// static
+void ThirdPartyUtil::Startup() {
+  nsCOMPtr<mozIThirdPartyUtil> tpu;
+  if (NS_WARN_IF(!(tpu = do_GetService(THIRDPARTYUTIL_CONTRACTID)))) {
+    NS_WARNING("Failed to get third party util!");
+  }
+}
+
 nsresult ThirdPartyUtil::Init() {
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE);
 
   MOZ_ASSERT(!gService);
   gService = this;
   mozilla::ClearOnShutdown(&gService);
 
   mTLDService = nsEffectiveTLDService::GetInstance();
@@ -343,8 +352,42 @@ ThirdPartyUtil::GetBaseDomain(nsIURI* aH
     aHostURI->SchemeIs("file", &isFileURI);
     if (!isFileURI) {
       return NS_ERROR_INVALID_ARG;
     }
   }
 
   return NS_OK;
 }
+
+NS_IMETHODIMP
+ThirdPartyUtil::GetBaseDomainFromSchemeHost(const nsACString& aScheme,
+                                            const nsACString& aAsciiHost,
+                                            nsACString& aBaseDomain) {
+  MOZ_DIAGNOSTIC_ASSERT(IsASCII(aAsciiHost));
+
+  // Get the base domain. this will fail if the host contains a leading dot,
+  // more than one trailing dot, or is otherwise malformed.
+  nsresult rv = mTLDService->GetBaseDomainFromHost(aAsciiHost, 0, aBaseDomain);
+  if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
+      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+    // aMozURL is either an IP address, an alias such as 'localhost', an eTLD
+    // such as 'co.uk', or the empty string. Uses the normalized host in such
+    // cases.
+    aBaseDomain = aAsciiHost;
+    rv = NS_OK;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // aMozURL (and thus aBaseDomain) may be the string '.'. If so, fail.
+  if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
+    return NS_ERROR_INVALID_ARG;
+
+  // Reject any URLs without a host that aren't file:// URLs. This makes it the
+  // only way we can get a base domain consisting of the empty string, which
+  // means we can safely perform foreign tests on such URLs where "not foreign"
+  // means "the involved URLs are all file://".
+  if (aBaseDomain.IsEmpty() && !aScheme.EqualsLiteral("file")) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return NS_OK;
+}
--- a/dom/base/ThirdPartyUtil.h
+++ b/dom/base/ThirdPartyUtil.h
@@ -12,21 +12,22 @@
 #include "mozIThirdPartyUtil.h"
 #include "nsEffectiveTLDService.h"
 #include "mozilla/Attributes.h"
 
 class nsIURI;
 
 class ThirdPartyUtil final : public mozIThirdPartyUtil {
  public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_MOZITHIRDPARTYUTIL
 
   nsresult Init();
 
+  static void Startup();
   static ThirdPartyUtil* GetInstance();
 
  private:
   ~ThirdPartyUtil();
 
   bool IsThirdPartyInternal(const nsCString& aFirstDomain,
                             const nsCString& aSecondDomain) {
     // Check strict equality.
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/dom/cache/AutoUtils.h"
 #include "mozilla/dom/cache/Cache.h"
 #include "mozilla/dom/cache/CacheChild.h"
 #include "mozilla/dom/cache/CacheStorageChild.h"
 #include "mozilla/dom/cache/CacheWorkerHolder.h"
 #include "mozilla/dom/cache/PCacheChild.h"
 #include "mozilla/dom/cache/ReadStream.h"
 #include "mozilla/dom/cache/TypeUtils.h"
+#include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/StaticPrefs.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/Document.h"
@@ -33,16 +34,17 @@
 #include "nsURLParsers.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 using mozilla::ErrorResult;
 using mozilla::Unused;
+using mozilla::dom::quota::QuotaManager;
 using mozilla::ipc::BackgroundChild;
 using mozilla::ipc::IProtocol;
 using mozilla::ipc::PBackgroundChild;
 using mozilla::ipc::PrincipalInfo;
 using mozilla::ipc::PrincipalToPrincipalInfo;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::CacheStorage);
 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::CacheStorage);
@@ -146,16 +148,22 @@ already_AddRefed<CacheStorage> CacheStor
 
   PrincipalInfo principalInfo;
   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(rv);
     return nullptr;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(principalInfo))) {
+    NS_WARNING("CacheStorage not supported on invalid origins.");
+    RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
+    return ref.forget();
+  }
+
   bool testingEnabled =
       aForceTrustedOrigin ||
       Preferences::GetBool("dom.caches.testing.enabled", false) ||
       StaticPrefs::dom_serviceWorkers_testing_enabled();
 
   if (!IsTrusted(principalInfo, testingEnabled)) {
     NS_WARNING("CacheStorage not supported on untrusted origins.");
     RefPtr<CacheStorage> ref = new CacheStorage(NS_ERROR_DOM_SECURITY_ERR);
@@ -186,16 +194,21 @@ already_AddRefed<CacheStorage> CacheStor
   if (!workerHolder) {
     NS_WARNING("Worker thread is shutting down.");
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   const PrincipalInfo& principalInfo = aWorkerPrivate->GetPrincipalInfo();
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(principalInfo))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
   // We have a number of cases where we want to skip the https scheme
   // validation:
   //
   // 1) Any worker when dom.caches.testing.enabled pref is true.
   // 2) Any worker when dom.serviceWorkers.testing.enabled pref is true.  This
   //    is mainly because most sites using SWs will expect Cache to work if
   //    SWs are enabled.
   // 3) If the window that created this worker has the devtools SW testing
--- a/dom/cache/CacheStorageParent.cpp
+++ b/dom/cache/CacheStorageParent.cpp
@@ -5,29 +5,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/CacheStorageParent.h"
 
 #include "mozilla/Unused.h"
 #include "mozilla/dom/cache/ActorUtils.h"
 #include "mozilla/dom/cache/CacheOpParent.h"
 #include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
+using mozilla::dom::quota::QuotaManager;
 using mozilla::ipc::PBackgroundParent;
 using mozilla::ipc::PrincipalInfo;
 
 // declared in ActorUtils.h
 PCacheStorageParent* AllocPCacheStorageParent(
     PBackgroundParent* aManagingActor, Namespace aNamespace,
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
+    MOZ_ASSERT(false);
+    return nullptr;
+  }
+
   return new CacheStorageParent(aManagingActor, aNamespace, aPrincipalInfo);
 }
 
 // declared in ActorUtils.h
 void DeallocPCacheStorageParent(PCacheStorageParent* aActor) { delete aActor; }
 
 CacheStorageParent::CacheStorageParent(PBackgroundParent* aManagingActor,
                                        Namespace aNamespace,
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/dom/ResponseBinding.h"
 #include "mozilla/dom/cache/CacheTypes.h"
 #include "mozilla/dom/cache/SavedTypes.h"
 #include "mozilla/dom/cache/Types.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "mozilla/net/MozURL.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatement.h"
+#include "mozIThirdPartyUtil.h"
 #include "mozStorageHelper.h"
 #include "nsCOMPtr.h"
 #include "nsCRT.h"
 #include "nsHttp.h"
 #include "nsIContentPolicy.h"
 #include "nsICryptoHash.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
@@ -2488,19 +2489,27 @@ nsresult ReadResponse(mozIStorageConnect
     MOZ_ASSERT(scheme == "http" || scheme == "https" || scheme == "file");
 #endif
 
     nsCString origin;
     url->Origin(origin);
 
     // CSP is recovered from the headers, no need to initialise it here.
     nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
+
+    nsCString baseDomain;
+    rv = url->BaseDomain(baseDomain);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
     aSavedResponseOut->mValue.principalInfo() =
         Some(mozilla::ipc::ContentPrincipalInfo(
-            attrs, origin, specNoSuffix, Nothing(), std::move(policies)));
+            attrs, origin, specNoSuffix, Nothing(), std::move(policies),
+            baseDomain));
   }
 
   bool nullPadding = false;
   rv = state->GetIsNull(6, &nullPadding);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
--- a/dom/clients/manager/ClientManagerParent.cpp
+++ b/dom/clients/manager/ClientManagerParent.cpp
@@ -71,17 +71,24 @@ PClientNavigateOpParent* ClientManagerPa
 bool ClientManagerParent::DeallocPClientNavigateOpParent(
     PClientNavigateOpParent* aActor) {
   delete aActor;
   return true;
 }
 
 PClientSourceParent* ClientManagerParent::AllocPClientSourceParent(
     const ClientSourceConstructorArgs& aArgs) {
-  return new ClientSourceParent(aArgs);
+  Maybe<ContentParentId> contentParentId;
+
+  uint64_t childID = BackgroundParent::GetChildID(Manager());
+  if (childID) {
+    contentParentId = Some(ContentParentId(childID));
+  }
+
+  return new ClientSourceParent(aArgs, contentParentId);
 }
 
 bool ClientManagerParent::DeallocPClientSourceParent(
     PClientSourceParent* aActor) {
   delete aActor;
   return true;
 }
 
--- a/dom/clients/manager/ClientManagerService.cpp
+++ b/dom/clients/manager/ClientManagerService.cpp
@@ -606,10 +606,35 @@ RefPtr<ClientOpPromise> ClientManagerSer
 
   nsCOMPtr<nsIRunnable> r =
       new OpenWindowRunnable(promise, aArgs, std::move(aSourceProcess));
   MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
 
   return promise.forget();
 }
 
+bool ClientManagerService::HasWindow(
+    const Maybe<ContentParentId>& aContentParentId,
+    const PrincipalInfo& aPrincipalInfo, const nsID& aClientId) {
+  AssertIsOnBackgroundThread();
+
+  ClientSourceParent* source = FindSource(aClientId, aPrincipalInfo);
+  if (!source) {
+    return false;
+  }
+
+  if (!source->ExecutionReady()) {
+    return false;
+  }
+
+  if (source->Info().Type() != ClientType::Window) {
+    return false;
+  }
+
+  if (aContentParentId && !source->IsOwnedByProcess(aContentParentId.value())) {
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/clients/manager/ClientManagerService.h
+++ b/dom/clients/manager/ClientManagerService.h
@@ -6,16 +6,22 @@
 #ifndef _mozilla_dom_ClientManagerService_h
 #define _mozilla_dom_ClientManagerService_h
 
 #include "ClientOpPromise.h"
 #include "nsDataHashtable.h"
 
 namespace mozilla {
 
+namespace ipc {
+
+class PrincipalInfo;
+
+}  // namespace ipc
+
 namespace dom {
 
 class ClientManagerParent;
 class ClientSourceParent;
 class ContentParent;
 
 // Define a singleton service to manage client activity throughout the
 // browser.  This service runs on the PBackground thread.  To interact
@@ -59,15 +65,19 @@ class ClientManagerService final {
 
   RefPtr<ClientOpPromise> GetInfoAndState(
       const ClientGetInfoAndStateArgs& aArgs);
 
   RefPtr<ClientOpPromise> OpenWindow(
       const ClientOpenWindowArgs& aArgs,
       already_AddRefed<ContentParent> aSourceProcess);
 
+  bool HasWindow(const Maybe<ContentParentId>& aContentParentId,
+                 const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                 const nsID& aClientId);
+
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::ClientManagerService)
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // _mozilla_dom_ClientManagerService_h
--- a/dom/clients/manager/ClientSourceParent.cpp
+++ b/dom/clients/manager/ClientSourceParent.cpp
@@ -196,19 +196,22 @@ PClientSourceOpParent* ClientSourceParen
 }
 
 bool ClientSourceParent::DeallocPClientSourceOpParent(
     PClientSourceOpParent* aActor) {
   delete aActor;
   return true;
 }
 
-ClientSourceParent::ClientSourceParent(const ClientSourceConstructorArgs& aArgs)
+ClientSourceParent::ClientSourceParent(
+    const ClientSourceConstructorArgs& aArgs,
+    const Maybe<ContentParentId>& aContentParentId)
     : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(),
                   aArgs.creationTime()),
+      mContentParentId(aContentParentId),
       mService(ClientManagerService::GetOrCreateInstance()),
       mExecutionReady(false),
       mFrozen(false) {}
 
 ClientSourceParent::~ClientSourceParent() {
   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
 
   mExecutionReadyPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
--- a/dom/clients/manager/ClientSourceParent.h
+++ b/dom/clients/manager/ClientSourceParent.h
@@ -5,27 +5,29 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef _mozilla_dom_ClientSourceParent_h
 #define _mozilla_dom_ClientSourceParent_h
 
 #include "ClientInfo.h"
 #include "ClientOpPromise.h"
 #include "mozilla/dom/PClientSourceParent.h"
 #include "mozilla/dom/ServiceWorkerDescriptor.h"
+#include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/MozPromise.h"
 
 namespace mozilla {
 namespace dom {
 
 class ClientHandleParent;
 class ClientManagerService;
 
 class ClientSourceParent final : public PClientSourceParent {
   ClientInfo mClientInfo;
   Maybe<ServiceWorkerDescriptor> mController;
+  const Maybe<ContentParentId> mContentParentId;
   RefPtr<ClientManagerService> mService;
   nsTArray<ClientHandleParent*> mHandleList;
   MozPromiseHolder<GenericPromise> mExecutionReadyPromise;
   bool mExecutionReady;
   bool mFrozen;
 
   void KillInvalidChild();
 
@@ -49,33 +51,38 @@ class ClientSourceParent final : public 
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   PClientSourceOpParent* AllocPClientSourceOpParent(
       const ClientOpConstructorArgs& aArgs) override;
 
   bool DeallocPClientSourceOpParent(PClientSourceOpParent* aActor) override;
 
  public:
-  explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs);
+  explicit ClientSourceParent(const ClientSourceConstructorArgs& aArgs,
+                              const Maybe<ContentParentId>& aContentParentId);
   ~ClientSourceParent();
 
   void Init();
 
   const ClientInfo& Info() const;
 
   bool IsFrozen() const;
 
   bool ExecutionReady() const;
 
   RefPtr<GenericPromise> ExecutionReadyPromise();
 
   const Maybe<ServiceWorkerDescriptor>& GetController() const;
 
   void ClearController();
 
+  bool IsOwnedByProcess(ContentParentId aContentParentId) const {
+    return mContentParentId && mContentParentId.value() == aContentParentId;
+  }
+
   void AttachHandle(ClientHandleParent* aClientSource);
 
   void DetachHandle(ClientHandleParent* aClientSource);
 
   RefPtr<ClientOpPromise> StartOp(const ClientOpConstructorArgs& aArgs);
 };
 
 }  // namespace dom
--- a/dom/clients/manager/moz.build
+++ b/dom/clients/manager/moz.build
@@ -6,16 +6,17 @@
 
 EXPORTS.mozilla.dom += [
   'ClientChannelHelper.h',
   'ClientHandle.h',
   'ClientInfo.h',
   'ClientIPCUtils.h',
   'ClientManager.h',
   'ClientManagerActors.h',
+  'ClientManagerService.h',
   'ClientOpenWindowOpActors.h',
   'ClientOpPromise.h',
   'ClientSource.h',
   'ClientState.h',
   'ClientThing.h',
 ]
 
 UNIFIED_SOURCES += [
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -267,16 +267,18 @@ const char kPrefFileHandleEnabled[] = "d
 // begins deleting a database. It is removed as the last step of deletion. If a
 // deletion marker file is found when initializing the origin, the deletion
 // routine is run again to ensure that the database and all of its related files
 // are removed. The primary goal of this mechanism is to avoid situations where
 // a database has been partially deleted, leading to inconsistent state for the
 // origin.
 #define IDB_DELETION_MARKER_FILE_PREFIX "idb-deleting-"
 
+const uint32_t kDeleteTimeoutMs = 1000;
+
 #ifdef DEBUG
 
 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGThreadSleepMS = 0;
 
 const int32_t kDEBUGTransactionThreadPriority =
     nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGTransactionThreadSleepMS = 0;
@@ -7660,27 +7662,16 @@ class GetFileReferencesHelper final : pu
                                            bool* aResult);
 
  private:
   ~GetFileReferencesHelper() override = default;
 
   NS_DECL_NSIRUNNABLE
 };
 
-class FlushPendingFileDeletionsRunnable final : public Runnable {
- public:
-  FlushPendingFileDeletionsRunnable()
-      : Runnable("FlushPendingFileDeletionsRunnable") {}
-
- private:
-  ~FlushPendingFileDeletionsRunnable() override = default;
-
-  NS_DECL_NSIRUNNABLE
-};
-
 class PermissionRequestHelper final : public PermissionRequestBase,
                                       public PIndexedDBPermissionRequestParent {
   bool mActorDestroyed;
 
  public:
   PermissionRequestHelper(Element* aOwnerElement, nsIPrincipal* aPrincipal)
       : PermissionRequestBase(aOwnerElement, aPrincipal),
         mActorDestroyed(false) {}
@@ -7771,19 +7762,22 @@ class DatabaseLoggingInfo final {
  private:
   ~DatabaseLoggingInfo();
 };
 
 class QuotaClient final : public mozilla::dom::quota::Client {
   static QuotaClient* sInstance;
 
   nsCOMPtr<nsIEventTarget> mBackgroundThread;
+  nsCOMPtr<nsITimer> mDeleteTimer;
   nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
   RefPtr<Maintenance> mCurrentMaintenance;
   RefPtr<nsThreadPool> mMaintenanceThreadPool;
+  nsClassHashtable<nsRefPtrHashKey<FileManager>, nsTArray<int64_t>>
+      mPendingDeleteInfos;
   bool mShutdownRequested;
 
  public:
   QuotaClient();
 
   static QuotaClient* GetInstance() {
     AssertIsOnBackgroundThread();
 
@@ -7812,16 +7806,20 @@ class QuotaClient final : public mozilla
   }
 
   bool IsShuttingDown() const {
     AssertIsOnBackgroundThread();
 
     return mShutdownRequested;
   }
 
+  nsresult AsyncDeleteFile(FileManager* aFileManager, int64_t aFileId);
+
+  nsresult FlushPendingFileDeletions();
+
   already_AddRefed<Maintenance> GetCurrentMaintenance() const {
     RefPtr<Maintenance> result = mCurrentMaintenance;
     return result.forget();
   }
 
   void NoteFinishedMaintenance(Maintenance* aMaintenance) {
     AssertIsOnBackgroundThread();
     MOZ_ASSERT(aMaintenance);
@@ -7861,23 +7859,21 @@ class QuotaClient final : public mozilla
   void AbortOperationsForProcess(ContentParentId aContentParentId) override;
 
   void StartIdleMaintenance() override;
 
   void StopIdleMaintenance() override;
 
   void ShutdownWorkThreads() override;
 
-  void DidInitialize(QuotaManager* aQuotaManager) override;
-
-  void WillShutdown() override;
-
  private:
   ~QuotaClient() override;
 
+  static void DeleteTimerCallback(nsITimer* aTimer, void* aClosure);
+
   nsresult GetDirectory(PersistenceType aPersistenceType,
                         const nsACString& aOrigin, nsIFile** aDirectory);
 
   // The aObsoleteFiles will collect files based on the marker files. For now,
   // InitOrigin() is the only consumer of this argument because it checks those
   // unfinished deletion and clean them up after that.
   nsresult GetDatabaseFilenames(
       nsIFile* aDirectory, const AtomicBool& aCanceled, bool aForUpgrade,
@@ -7890,16 +7886,76 @@ class QuotaClient final : public mozilla
                                         UsageInfo* aUsageInfo,
                                         bool aDatabaseFiles);
 
   // Runs on the PBackground thread. Checks to see if there's a queued
   // Maintenance to run.
   void ProcessMaintenanceQueue();
 };
 
+class DeleteFilesRunnable final : public Runnable,
+                                  public OpenDirectoryListener {
+  typedef mozilla::dom::quota::DirectoryLock DirectoryLock;
+
+  enum State {
+    // Just created on the PBackground thread. Next step is
+    // State_DirectoryOpenPending.
+    State_Initial,
+
+    // Waiting for directory open allowed on the main thread. The next step is
+    // State_DatabaseWorkOpen.
+    State_DirectoryOpenPending,
+
+    // Waiting to do/doing work on the QuotaManager IO thread. The next step is
+    // State_UnblockingOpen.
+    State_DatabaseWorkOpen,
+
+    // Notifying the QuotaManager that it can proceed to the next operation on
+    // the main thread. Next step is State_Completed.
+    State_UnblockingOpen,
+
+    // All done.
+    State_Completed
+  };
+
+  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+  RefPtr<FileManager> mFileManager;
+  RefPtr<DirectoryLock> mDirectoryLock;
+  nsCOMPtr<nsIFile> mDirectory;
+  nsCOMPtr<nsIFile> mJournalDirectory;
+  nsTArray<int64_t> mFileIds;
+  State mState;
+
+ public:
+  DeleteFilesRunnable(FileManager* aFileManager, nsTArray<int64_t>&& aFileIds);
+
+  void RunImmediately();
+
+ private:
+  ~DeleteFilesRunnable() = default;
+
+  nsresult Open();
+
+  nsresult DeleteFile(int64_t aFileId);
+
+  nsresult DoDatabaseWork();
+
+  void Finish();
+
+  void UnblockOpen();
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+
+  // OpenDirectoryListener overrides.
+  virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
+
+  virtual void DirectoryLockFailed() override;
+};
+
 class Maintenance final : public Runnable, public OpenDirectoryListener {
   struct DirectoryInfo;
 
   enum class State {
     // Newly created on the PBackground thread. Will proceed immediately or be
     // added to the maintenance queue. The next step is either
     // DirectoryOpenPending if IndexedDatabaseManager is running, or
     // CreateIndexedDatabaseManager if not.
@@ -9140,20 +9196,22 @@ bool DeallocPBackgroundIndexedDBUtilsPar
 
   RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
   return true;
 }
 
 bool RecvFlushPendingFileDeletions() {
   AssertIsOnBackgroundThread();
 
-  RefPtr<FlushPendingFileDeletionsRunnable> runnable =
-      new FlushPendingFileDeletionsRunnable();
-
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
+  QuotaClient* quotaClient = QuotaClient::GetInstance();
+  if (quotaClient) {
+    if (NS_FAILED(quotaClient->FlushPendingFileDeletions())) {
+      NS_WARNING("Failed to flush pending file deletions!");
+    }
+  }
 
   return true;
 }
 
 PIndexedDBPermissionRequestParent* AllocPIndexedDBPermissionRequestParent(
     Element* aOwnerElement, nsIPrincipal* aPrincipal) {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -9211,16 +9269,30 @@ FileHandleThreadPool* GetFileHandleThrea
     }
 
     gFileHandleThreadPool = fileHandleThreadPool;
   }
 
   return gFileHandleThreadPool;
 }
 
+nsresult AsyncDeleteFile(FileManager* aFileManager, int64_t aFileId) {
+  AssertIsOnBackgroundThread();
+
+  QuotaClient* quotaClient = QuotaClient::GetInstance();
+  if (quotaClient) {
+    nsresult rv = quotaClient->AsyncDeleteFile(aFileManager, aFileId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
 /*******************************************************************************
  * DatabaseConnection implementation
  ******************************************************************************/
 
 DatabaseConnection::DatabaseConnection(
     mozIStorageConnection* aStorageConnection, FileManager* aFileManager)
     : mStorageConnection(aStorageConnection),
       mFileManager(aFileManager),
@@ -12172,16 +12244,21 @@ Factory::AllocPBackgroundIDBFactoryReque
   }
 
   if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
                  metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(principalInfo))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
   RefPtr<ContentParent> contentParent =
       BackgroundParent::GetContentParent(Manager());
 
   RefPtr<FactoryOp> actor;
   if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
     actor = new OpenDatabaseOp(this, contentParent.forget(), *commonParams);
   } else {
     actor = new DeleteDatabaseOp(this, contentParent.forget(), *commonParams);
@@ -15648,17 +15725,18 @@ nsresult FileManager::GetUsage(nsIFile* 
 }
 
 /*******************************************************************************
  * QuotaClient
  ******************************************************************************/
 
 QuotaClient* QuotaClient::sInstance = nullptr;
 
-QuotaClient::QuotaClient() : mShutdownRequested(false) {
+QuotaClient::QuotaClient()
+    : mDeleteTimer(NS_NewTimer()), mShutdownRequested(false) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
   MOZ_ASSERT(!gTelemetryIdMutex);
 
   // Always create this so that later access to gTelemetryIdHashtable can be
   // properly synchronized.
   gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex");
 
@@ -15674,16 +15752,54 @@ QuotaClient::~QuotaClient() {
   // No one else should be able to touch gTelemetryIdHashtable now that the
   // QuotaClient has gone away.
   gTelemetryIdHashtable = nullptr;
   gTelemetryIdMutex = nullptr;
 
   sInstance = nullptr;
 }
 
+nsresult QuotaClient::AsyncDeleteFile(FileManager* aFileManager,
+                                      int64_t aFileId) {
+  AssertIsOnBackgroundThread();
+  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;
+  }
+
+  nsTArray<int64_t>* array;
+  if (!mPendingDeleteInfos.Get(aFileManager, &array)) {
+    array = new nsTArray<int64_t>();
+    mPendingDeleteInfos.Put(aFileManager, array);
+  }
+
+  array->AppendElement(aFileId);
+
+  return NS_OK;
+}
+
+nsresult QuotaClient::FlushPendingFileDeletions() {
+  AssertIsOnBackgroundThread();
+
+  nsresult rv = mDeleteTimer->Cancel();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  DeleteTimerCallback(mDeleteTimer, this);
+
+  return NS_OK;
+}
+
 nsThreadPool* QuotaClient::GetOrCreateThreadPool() {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mShutdownRequested);
 
   if (!mMaintenanceThreadPool) {
     RefPtr<nsThreadPool> threadPool = new nsThreadPool();
 
     // PR_GetNumberOfProcessors() can return -1 on error, so make sure we
@@ -16124,32 +16240,47 @@ void QuotaClient::ShutdownWorkThreads() 
 
     gFileHandleThreadPool = nullptr;
   }
 
   if (mMaintenanceThreadPool) {
     mMaintenanceThreadPool->Shutdown();
     mMaintenanceThreadPool = nullptr;
   }
-}
-
-void QuotaClient::DidInitialize(QuotaManager* aQuotaManager) {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
-    mgr->NoteLiveQuotaManager(aQuotaManager);
-  }
-}
-
-void QuotaClient::WillShutdown() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
-    mgr->NoteShuttingDownQuotaManager();
-  }
+
+  if (mDeleteTimer) {
+    MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
+    mDeleteTimer = nullptr;
+  }
+}
+
+void QuotaClient::DeleteTimerCallback(nsITimer* aTimer, void* aClosure) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aTimer);
+
+  auto* self = static_cast<QuotaClient*>(aClosure);
+  MOZ_ASSERT(self);
+  MOZ_ASSERT(self->mDeleteTimer);
+  MOZ_ASSERT(SameCOMIdentity(self->mDeleteTimer, aTimer));
+
+  for (auto iter = self->mPendingDeleteInfos.ConstIter(); !iter.Done();
+       iter.Next()) {
+    auto key = iter.Key();
+    auto value = iter.Data();
+    MOZ_ASSERT(!value->IsEmpty());
+
+    RefPtr<DeleteFilesRunnable> runnable =
+        new DeleteFilesRunnable(key, std::move(*value));
+
+    MOZ_ASSERT(value->IsEmpty());
+
+    runnable->RunImmediately();
+  }
+
+  self->mPendingDeleteInfos.Clear();
 }
 
 nsresult QuotaClient::GetDirectory(PersistenceType aPersistenceType,
                                    const nsACString& aOrigin,
                                    nsIFile** aDirectory) {
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
 
@@ -16388,16 +16519,192 @@ void QuotaClient::ProcessMaintenanceQueu
   }
 
   mCurrentMaintenance = mMaintenanceQueue[0];
   mMaintenanceQueue.RemoveElementAt(0);
 
   mCurrentMaintenance->RunImmediately();
 }
 
+/*******************************************************************************
+ * DeleteFilesRunnable
+ ******************************************************************************/
+
+DeleteFilesRunnable::DeleteFilesRunnable(FileManager* aFileManager,
+                                         nsTArray<int64_t>&& aFileIds)
+    : Runnable("dom::indexeddb::DeleteFilesRunnable"),
+      mOwningEventTarget(GetCurrentThreadEventTarget()),
+      mFileManager(aFileManager),
+      mFileIds(std::move(aFileIds)),
+      mState(State_Initial) {}
+
+void DeleteFilesRunnable::RunImmediately() {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial);
+
+  Unused << this->Run();
+}
+
+nsresult DeleteFilesRunnable::Open() {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  if (NS_WARN_IF(!quotaManager)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mState = State_DirectoryOpenPending;
+
+  quotaManager->OpenDirectory(mFileManager->Type(), mFileManager->Group(),
+                              mFileManager->Origin(), quota::Client::IDB,
+                              /* aExclusive */ false, this);
+
+  return NS_OK;
+}
+
+nsresult DeleteFilesRunnable::DeleteFile(int64_t aFileId) {
+  MOZ_ASSERT(mDirectory);
+  MOZ_ASSERT(mJournalDirectory);
+
+  nsCOMPtr<nsIFile> file = mFileManager->GetFileForId(mDirectory, aFileId);
+  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+  nsresult rv;
+  int64_t fileSize;
+
+  if (mFileManager->EnforcingQuota()) {
+    rv = file->GetFileSize(&fileSize);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+  }
+
+  rv = file->Remove(false);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+  if (mFileManager->EnforcingQuota()) {
+    QuotaManager* quotaManager = QuotaManager::Get();
+    NS_ASSERTION(quotaManager, "Shouldn't be null!");
+
+    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
+                                         mFileManager->Group(),
+                                         mFileManager->Origin(), fileSize);
+  }
+
+  file = mFileManager->GetFileForId(mJournalDirectory, aFileId);
+  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
+
+  rv = file->Remove(false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult DeleteFilesRunnable::DoDatabaseWork() {
+  AssertIsOnIOThread();
+  MOZ_ASSERT(mState == State_DatabaseWorkOpen);
+
+  if (!mFileManager->Invalidated()) {
+    mDirectory = mFileManager->GetDirectory();
+    if (NS_WARN_IF(!mDirectory)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mJournalDirectory = mFileManager->GetJournalDirectory();
+    if (NS_WARN_IF(!mJournalDirectory)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    for (int64_t fileId : mFileIds) {
+      if (NS_FAILED(DeleteFile(fileId))) {
+        NS_WARNING("Failed to delete file!");
+      }
+    }
+  }
+
+  Finish();
+
+  return NS_OK;
+}
+
+void DeleteFilesRunnable::Finish() {
+  // Must set mState before dispatching otherwise we will race with the main
+  // thread.
+  mState = State_UnblockingOpen;
+
+  MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+void DeleteFilesRunnable::UnblockOpen() {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_UnblockingOpen);
+
+  mDirectoryLock = nullptr;
+
+  mState = State_Completed;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(DeleteFilesRunnable, Runnable)
+
+NS_IMETHODIMP
+DeleteFilesRunnable::Run() {
+  nsresult rv;
+
+  switch (mState) {
+    case State_Initial:
+      rv = Open();
+      break;
+
+    case State_DatabaseWorkOpen:
+      rv = DoDatabaseWork();
+      break;
+
+    case State_UnblockingOpen:
+      UnblockOpen();
+      return NS_OK;
+
+    case State_DirectoryOpenPending:
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
+    Finish();
+  }
+
+  return NS_OK;
+}
+
+void DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  mDirectoryLock = aLock;
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  // Must set this before dispatching otherwise we will race with the IO thread
+  mState = State_DatabaseWorkOpen;
+
+  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Finish();
+    return;
+  }
+}
+
+void DeleteFilesRunnable::DirectoryLockFailed() {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_DirectoryOpenPending);
+  MOZ_ASSERT(!mDirectoryLock);
+
+  Finish();
+}
+
 void Maintenance::RegisterDatabaseMaintenance(
     DatabaseMaintenance* aDatabaseMaintenance) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabaseMaintenance);
   MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
   MOZ_ASSERT(!mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
 
   mDatabaseMaintenances.Put(aDatabaseMaintenance->DatabasePath(),
@@ -26293,33 +26600,16 @@ GetFileReferencesHelper::Run() {
   MOZ_ASSERT(mWaiting);
 
   mWaiting = false;
   mCondVar.Notify();
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-FlushPendingFileDeletionsRunnable::Run() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
-  if (NS_WARN_IF(!mgr)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = mgr->FlushPendingFileDeletions();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
 void PermissionRequestHelper::OnPromptComplete(
     PermissionValue aPermissionValue) {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mActorDestroyed) {
     Unused << PIndexedDBPermissionRequestParent::Send__delete__(
         this, aPermissionValue);
   }
--- a/dom/indexedDB/ActorsParent.h
+++ b/dom/indexedDB/ActorsParent.h
@@ -2,16 +2,20 @@
 /* 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_indexeddb_actorsparent_h__
 #define mozilla_dom_indexeddb_actorsparent_h__
 
+#include "nscore.h"
+
+#include <stdint.h>
+
 template <class>
 struct already_AddRefed;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
 class Element;
@@ -20,16 +24,17 @@ class FileHandleThreadPool;
 namespace quota {
 
 class Client;
 
 }  // namespace quota
 
 namespace indexedDB {
 
+class FileManager;
 class LoggingInfo;
 class PBackgroundIDBFactoryParent;
 class PBackgroundIndexedDBUtilsParent;
 class PIndexedDBPermissionRequestParent;
 
 PBackgroundIDBFactoryParent* AllocPBackgroundIDBFactoryParent(
     const LoggingInfo& aLoggingInfo);
 
@@ -53,13 +58,15 @@ bool RecvPIndexedDBPermissionRequestCons
 
 bool DeallocPIndexedDBPermissionRequestParent(
     PIndexedDBPermissionRequestParent* aActor);
 
 already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient();
 
 FileHandleThreadPool* GetFileHandleThreadPool();
 
+nsresult AsyncDeleteFile(FileManager* aFileManager, int64_t aFileId);
+
 }  // namespace indexedDB
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_indexeddb_actorsparent_h__
--- a/dom/indexedDB/FileInfo.cpp
+++ b/dom/indexedDB/FileInfo.cpp
@@ -1,30 +1,33 @@
 /* -*- 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 "FileInfo.h"
 
+#include "ActorsParent.h"
 #include "FileManager.h"
 #include "IndexedDatabaseManager.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "nsError.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
 
 namespace {
 
 template <typename IdType>
 class FileInfoImpl final : public FileInfo {
   IdType mFileId;
 
  public:
@@ -35,39 +38,16 @@ class FileInfoImpl final : public FileIn
   }
 
  private:
   ~FileInfoImpl() {}
 
   virtual int64_t Id() const override { return int64_t(mFileId); }
 };
 
-class CleanupFileRunnable final : public Runnable {
-  RefPtr<FileManager> mFileManager;
-  int64_t mFileId;
-
- public:
-  static void DoCleanup(FileManager* aFileManager, int64_t aFileId);
-
-  CleanupFileRunnable(FileManager* aFileManager, int64_t aFileId)
-      : Runnable("dom::indexedDB::CleanupFileRunnable"),
-        mFileManager(aFileManager),
-        mFileId(aFileId) {
-    MOZ_ASSERT(aFileManager);
-    MOZ_ASSERT(aFileId > 0);
-  }
-
-  NS_INLINE_DECL_REFCOUNTING_INHERITED(CleanupFileRunnable, Runnable);
-
- private:
-  ~CleanupFileRunnable() {}
-
-  NS_DECL_NSIRUNNABLE
-};
-
 }  // namespace
 
 FileInfo::FileInfo(FileManager* aFileManager) : mFileManager(aFileManager) {
   MOZ_ASSERT(aFileManager);
 }
 
 FileInfo::~FileInfo() {}
 
@@ -175,58 +155,25 @@ bool FileInfo::LockedClearDBRefs() {
   MOZ_ASSERT(mFileManager->Invalidated());
 
   delete this;
 
   return false;
 }
 
 void FileInfo::Cleanup() {
+  AssertIsOnBackgroundThread();
+
   int64_t id = Id();
 
-  // IndexedDatabaseManager is main-thread only.
-  if (!NS_IsMainThread()) {
-    RefPtr<CleanupFileRunnable> cleaner =
-        new CleanupFileRunnable(mFileManager, id);
-
-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(cleaner));
-    return;
-  }
-
-  CleanupFileRunnable::DoCleanup(mFileManager, id);
-}
-
-// static
-void CleanupFileRunnable::DoCleanup(FileManager* aFileManager,
-                                    int64_t aFileId) {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aFileManager);
-  MOZ_ASSERT(aFileId > 0);
-
-  if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
-    return;
-  }
-
-  RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
-  MOZ_ASSERT(mgr);
-
-  if (NS_FAILED(mgr->AsyncDeleteFile(aFileManager, aFileId))) {
+  if (NS_FAILED(AsyncDeleteFile(mFileManager, id))) {
     NS_WARNING("Failed to delete file asynchronously!");
   }
 }
 
-NS_IMETHODIMP
-CleanupFileRunnable::Run() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  DoCleanup(mFileManager, mFileId);
-
-  return NS_OK;
-}
-
 /* static */
 already_AddRefed<nsIFile> FileInfo::GetFileForFileInfo(FileInfo* aFileInfo) {
   FileManager* fileManager = aFileInfo->Manager();
   nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
   if (NS_WARN_IF(!directory)) {
     return nullptr;
   }
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -128,16 +128,21 @@ nsresult IDBFactory::CreateForWindow(nsP
   if (NS_WARN_IF(NS_FAILED(rv))) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo ||
              principalInfo->type() == PrincipalInfo::TSystemPrincipalInfo);
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(*principalInfo))) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
   nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow);
   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
 
   RefPtr<IDBFactory> factory = new IDBFactory();
   factory->mPrincipalInfo = std::move(principalInfo);
   factory->mWindow = aWindow;
   factory->mTabChild = TabChild::GetFrom(aWindow);
   factory->mEventTarget =
@@ -164,16 +169,20 @@ nsresult IDBFactory::CreateForMainThread
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(*principalInfo))) {
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
   rv = CreateForMainThreadJSInternal(aCx, aOwningObject, principalInfo,
                                      aFactory);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(!principalInfo);
 
@@ -579,16 +588,22 @@ already_AddRefed<IDBOpenDBRequest> IDBFa
     }
 
     if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
         principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
       IDB_REPORT_INTERNAL_ERR();
       aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
       return nullptr;
     }
+
+    if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(principalInfo))) {
+      IDB_REPORT_INTERNAL_ERR();
+      aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+      return nullptr;
+    }
   } else {
     principalInfo = *mPrincipalInfo;
   }
 
   uint64_t version = 0;
   if (!aDeleting && aVersion.WasPassed()) {
     if (aVersion.Value() < 1) {
       aRv.ThrowTypeError<MSG_INVALID_VERSION>();
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -4,42 +4,35 @@
  * 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 "IndexedDatabaseManager.h"
 
 #include "chrome/common/ipc_channel.h"  // for IPC::Channel::kMaximumMessageSize
 #include "nsIConsoleService.h"
 #include "nsIDOMWindow.h"
-#include "nsIEventTarget.h"
-#include "nsIFile.h"
 #include "nsIScriptError.h"
 #include "nsIScriptGlobalObject.h"
 
 #include "jsapi.h"
 #include "mozilla/ClearOnShutdown.h"
-#include "mozilla/CondVar.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ErrorEventBinding.h"
-#include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/ipc/BackgroundChild.h"
-#include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
-#include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 
-#include "FileInfo.h"
 #include "FileManager.h"
 #include "IDBEvents.h"
 #include "IDBFactory.h"
 #include "IDBKeyRange.h"
 #include "IDBRequest.h"
 #include "ProfilerHelpers.h"
 #include "ScriptErrorHelper.h"
 #include "nsCharSeparatedTokenizer.h"
@@ -150,79 +143,16 @@ Atomic<bool> gInitialized(false);
 Atomic<bool> gClosed(false);
 Atomic<bool> gTestingMode(false);
 Atomic<bool> gExperimentalFeaturesEnabled(false);
 Atomic<bool> gFileHandleEnabled(false);
 Atomic<bool> gPrefErrorEventToSelfError(false);
 Atomic<int32_t> gDataThresholdBytes(0);
 Atomic<int32_t> gMaxSerializedMsgSize(0);
 
-class DeleteFilesRunnable final : public nsIRunnable,
-                                  public OpenDirectoryListener {
-  typedef mozilla::dom::quota::DirectoryLock DirectoryLock;
-
-  enum State {
-    // Just created on the main thread. Next step is State_DirectoryOpenPending.
-    State_Initial,
-
-    // Waiting for directory open allowed on the main thread. The next step is
-    // State_DatabaseWorkOpen.
-    State_DirectoryOpenPending,
-
-    // Waiting to do/doing work on the QuotaManager IO thread. The next step is
-    // State_UnblockingOpen.
-    State_DatabaseWorkOpen,
-
-    // Notifying the QuotaManager that it can proceed to the next operation on
-    // the main thread. Next step is State_Completed.
-    State_UnblockingOpen,
-
-    // All done.
-    State_Completed
-  };
-
-  nsCOMPtr<nsIEventTarget> mBackgroundThread;
-
-  RefPtr<FileManager> mFileManager;
-  nsTArray<int64_t> mFileIds;
-
-  RefPtr<DirectoryLock> mDirectoryLock;
-
-  nsCOMPtr<nsIFile> mDirectory;
-  nsCOMPtr<nsIFile> mJournalDirectory;
-
-  State mState;
-
- public:
-  DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
-                      FileManager* aFileManager, nsTArray<int64_t>& aFileIds);
-
-  void Dispatch();
-
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-  virtual void DirectoryLockAcquired(DirectoryLock* aLock) override;
-
-  virtual void DirectoryLockFailed() override;
-
- private:
-  ~DeleteFilesRunnable() {}
-
-  nsresult Open();
-
-  nsresult DeleteFile(int64_t aFileId);
-
-  nsresult DoDatabaseWork();
-
-  void Finish();
-
-  void UnblockOpen();
-};
-
 void AtomicBoolPrefChangedCallback(const char* aPrefName,
                                    Atomic<bool>* aClosure) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aClosure);
 
   *aClosure = Preferences::GetBool(aPrefName);
 }
 
@@ -312,27 +242,16 @@ IndexedDatabaseManager* IndexedDatabaseM
 IndexedDatabaseManager* IndexedDatabaseManager::Get() {
   // Does not return an owning reference.
   return gDBManager;
 }
 
 nsresult IndexedDatabaseManager::Init() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  // During Init() we can't yet call IsMainProcess(), just check sIsMainProcess
-  // directly.
-  if (sIsMainProcess) {
-    mDeleteTimer = NS_NewTimer();
-    NS_ENSURE_STATE(mDeleteTimer);
-
-    if (QuotaManager* quotaManager = QuotaManager::Get()) {
-      NoteLiveQuotaManager(quotaManager);
-    }
-  }
-
   Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
                                        kTestingPref, &gTestingMode);
   Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
                                        kPrefExperimental,
                                        &gExperimentalFeaturesEnabled);
   Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
                                        kPrefFileHandle, &gFileHandleEnabled);
   Preferences::RegisterCallbackAndCall(AtomicBoolPrefChangedCallback,
@@ -386,24 +305,16 @@ nsresult IndexedDatabaseManager::Init() 
 
 void IndexedDatabaseManager::Destroy() {
   // Setting the closed flag prevents the service from being recreated.
   // Don't set it though if there's no real instance created.
   if (gInitialized && gClosed.exchange(true)) {
     NS_ERROR("Shutdown more than once?!");
   }
 
-  if (sIsMainProcess && mDeleteTimer) {
-    if (NS_FAILED(mDeleteTimer->Cancel())) {
-      NS_WARNING("Failed to cancel timer!");
-    }
-
-    mDeleteTimer = nullptr;
-  }
-
   Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback, kTestingPref,
                                   &gTestingMode);
   Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
                                   kPrefExperimental,
                                   &gExperimentalFeaturesEnabled);
   Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
                                   kPrefFileHandle, &gFileHandleEnabled);
   Preferences::UnregisterCallback(AtomicBoolPrefChangedCallback,
@@ -702,34 +613,16 @@ uint32_t IndexedDatabaseManager::MaxSeri
 }
 
 void IndexedDatabaseManager::ClearBackgroundActor() {
   MOZ_ASSERT(NS_IsMainThread());
 
   mBackgroundActor = nullptr;
 }
 
-void IndexedDatabaseManager::NoteLiveQuotaManager(QuotaManager* aQuotaManager) {
-  // This can be called during Init, so we can't use IsMainProcess() yet.
-  MOZ_ASSERT(sIsMainProcess);
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aQuotaManager);
-
-  mBackgroundThread = aQuotaManager->OwningThread();
-}
-
-void IndexedDatabaseManager::NoteShuttingDownQuotaManager() {
-  MOZ_ASSERT(IsMainProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MOZ_ALWAYS_SUCCEEDS(mDeleteTimer->Cancel());
-
-  mBackgroundThread = nullptr;
-}
-
 already_AddRefed<FileManager> IndexedDatabaseManager::GetFileManager(
     PersistenceType aPersistenceType, const nsACString& aOrigin,
     const nsAString& aDatabaseName) {
   AssertIsOnIOThread();
 
   FileManagerInfo* info;
   if (!mFileManagerInfos.Get(aOrigin, &info)) {
     return nullptr;
@@ -796,50 +689,16 @@ void IndexedDatabaseManager::InvalidateF
 
   info->InvalidateAndRemoveFileManager(aPersistenceType, aDatabaseName);
 
   if (!info->HasFileManagers()) {
     mFileManagerInfos.Remove(aOrigin);
   }
 }
 
-nsresult IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager,
-                                                 int64_t aFileId) {
-  MOZ_ASSERT(IsMainProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aFileManager);
-  MOZ_ASSERT(aFileId > 0);
-  MOZ_ASSERT(mDeleteTimer);
-
-  if (!mBackgroundThread) {
-    return NS_OK;
-  }
-
-  nsresult rv = mDeleteTimer->Cancel();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = mDeleteTimer->InitWithCallback(this, kDeleteTimeoutMs,
-                                      nsITimer::TYPE_ONE_SHOT);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsTArray<int64_t>* array;
-  if (!mPendingDeleteInfos.Get(aFileManager, &array)) {
-    array = new nsTArray<int64_t>();
-    mPendingDeleteInfos.Put(aFileManager, array);
-  }
-
-  array->AppendElement(aFileId);
-
-  return NS_OK;
-}
-
 nsresult IndexedDatabaseManager::BlockAndGetFileReferences(
     PersistenceType aPersistenceType, const nsACString& aOrigin,
     const nsAString& aDatabaseName, int64_t aFileId, int32_t* aRefCnt,
     int32_t* aDBRefCnt, int32_t* aSliceRefCnt, bool* aResult) {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!InTestingMode())) {
     return NS_ERROR_UNEXPECTED;
@@ -878,35 +737,23 @@ nsresult IndexedDatabaseManager::BlockAn
 
 nsresult IndexedDatabaseManager::FlushPendingFileDeletions() {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!InTestingMode())) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (IsMainProcess()) {
-    nsresult rv = mDeleteTimer->Cancel();
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+  PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread();
+  if (NS_WARN_IF(!bgActor)) {
+    return NS_ERROR_FAILURE;
+  }
 
-    rv = Notify(mDeleteTimer);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  } else {
-    PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread();
-    if (NS_WARN_IF(!bgActor)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!bgActor->SendFlushPendingFileDeletions()) {
-      return NS_ERROR_FAILURE;
-    }
+  if (!bgActor->SendFlushPendingFileDeletions()) {
+    return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 // static
 void IndexedDatabaseManager::LoggingModePrefChangedCallback(
     const char* /* aPrefName */, void* /* aClosure */) {
@@ -945,50 +792,16 @@ void IndexedDatabaseManager::LoggingMode
 // static
 const nsCString& IndexedDatabaseManager::GetLocale() {
   IndexedDatabaseManager* idbManager = Get();
   MOZ_ASSERT(idbManager, "IDBManager is not ready!");
 
   return idbManager->mLocale;
 }
 
-NS_IMPL_ADDREF(IndexedDatabaseManager)
-NS_IMPL_RELEASE_WITH_DESTROY(IndexedDatabaseManager, Destroy())
-NS_IMPL_QUERY_INTERFACE(IndexedDatabaseManager, nsITimerCallback, nsINamed)
-
-NS_IMETHODIMP
-IndexedDatabaseManager::Notify(nsITimer* aTimer) {
-  MOZ_ASSERT(IsMainProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mBackgroundThread);
-
-  for (auto iter = mPendingDeleteInfos.ConstIter(); !iter.Done(); iter.Next()) {
-    auto key = iter.Key();
-    auto value = iter.Data();
-    MOZ_ASSERT(!value->IsEmpty());
-
-    RefPtr<DeleteFilesRunnable> runnable =
-        new DeleteFilesRunnable(mBackgroundThread, key, *value);
-
-    MOZ_ASSERT(value->IsEmpty());
-
-    runnable->Dispatch();
-  }
-
-  mPendingDeleteInfos.Clear();
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-IndexedDatabaseManager::GetName(nsACString& aName) {
-  aName.AssignLiteral("IndexedDatabaseManager");
-  return NS_OK;
-}
-
 already_AddRefed<FileManager> FileManagerInfo::GetFileManager(
     PersistenceType aPersistenceType, const nsAString& aName) const {
   AssertIsOnIOThread();
 
   const nsTArray<RefPtr<FileManager> >& managers =
       GetImmutableArray(aPersistenceType);
 
   for (uint32_t i = 0; i < managers.Length(); i++) {
@@ -1071,183 +884,10 @@ nsTArray<RefPtr<FileManager> >& FileMana
       return mDefaultStorageFileManagers;
 
     case PERSISTENCE_TYPE_INVALID:
     default:
       MOZ_CRASH("Bad storage type value!");
   }
 }
 
-DeleteFilesRunnable::DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
-                                         FileManager* aFileManager,
-                                         nsTArray<int64_t>& aFileIds)
-    : mBackgroundThread(aBackgroundThread),
-      mFileManager(aFileManager),
-      mState(State_Initial) {
-  mFileIds.SwapElements(aFileIds);
-}
-
-void DeleteFilesRunnable::Dispatch() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_Initial);
-
-  MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL));
-}
-
-NS_IMPL_ISUPPORTS(DeleteFilesRunnable, nsIRunnable)
-
-NS_IMETHODIMP
-DeleteFilesRunnable::Run() {
-  nsresult rv;
-
-  switch (mState) {
-    case State_Initial:
-      rv = Open();
-      break;
-
-    case State_DatabaseWorkOpen:
-      rv = DoDatabaseWork();
-      break;
-
-    case State_UnblockingOpen:
-      UnblockOpen();
-      return NS_OK;
-
-    case State_DirectoryOpenPending:
-    default:
-      MOZ_CRASH("Should never get here!");
-  }
-
-  if (NS_WARN_IF(NS_FAILED(rv)) && mState != State_UnblockingOpen) {
-    Finish();
-  }
-
-  return NS_OK;
-}
-
-void DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_DirectoryOpenPending);
-  MOZ_ASSERT(!mDirectoryLock);
-
-  mDirectoryLock = aLock;
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  // Must set this before dispatching otherwise we will race with the IO thread
-  mState = State_DatabaseWorkOpen;
-
-  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    Finish();
-    return;
-  }
-}
-
-void DeleteFilesRunnable::DirectoryLockFailed() {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_DirectoryOpenPending);
-  MOZ_ASSERT(!mDirectoryLock);
-
-  Finish();
-}
-
-nsresult DeleteFilesRunnable::Open() {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_Initial);
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  if (NS_WARN_IF(!quotaManager)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  mState = State_DirectoryOpenPending;
-
-  quotaManager->OpenDirectory(mFileManager->Type(), mFileManager->Group(),
-                              mFileManager->Origin(), quota::Client::IDB,
-                              /* aExclusive */ false, this);
-
-  return NS_OK;
-}
-
-nsresult DeleteFilesRunnable::DeleteFile(int64_t aFileId) {
-  MOZ_ASSERT(mDirectory);
-  MOZ_ASSERT(mJournalDirectory);
-
-  nsCOMPtr<nsIFile> file = mFileManager->GetFileForId(mDirectory, aFileId);
-  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
-
-  nsresult rv;
-  int64_t fileSize;
-
-  if (mFileManager->EnforcingQuota()) {
-    rv = file->GetFileSize(&fileSize);
-    NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-  }
-
-  rv = file->Remove(false);
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-
-  if (mFileManager->EnforcingQuota()) {
-    QuotaManager* quotaManager = QuotaManager::Get();
-    NS_ASSERTION(quotaManager, "Shouldn't be null!");
-
-    quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
-                                         mFileManager->Group(),
-                                         mFileManager->Origin(), fileSize);
-  }
-
-  file = mFileManager->GetFileForId(mJournalDirectory, aFileId);
-  NS_ENSURE_TRUE(file, NS_ERROR_FAILURE);
-
-  rv = file->Remove(false);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-nsresult DeleteFilesRunnable::DoDatabaseWork() {
-  AssertIsOnIOThread();
-  MOZ_ASSERT(mState == State_DatabaseWorkOpen);
-
-  if (!mFileManager->Invalidated()) {
-    mDirectory = mFileManager->GetDirectory();
-    if (NS_WARN_IF(!mDirectory)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    mJournalDirectory = mFileManager->GetJournalDirectory();
-    if (NS_WARN_IF(!mJournalDirectory)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    for (int64_t fileId : mFileIds) {
-      if (NS_FAILED(DeleteFile(fileId))) {
-        NS_WARNING("Failed to delete file!");
-      }
-    }
-  }
-
-  Finish();
-
-  return NS_OK;
-}
-
-void DeleteFilesRunnable::Finish() {
-  // Must set mState before dispatching otherwise we will race with the main
-  // thread.
-  mState = State_UnblockingOpen;
-
-  MOZ_ALWAYS_SUCCEEDS(mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL));
-}
-
-void DeleteFilesRunnable::UnblockOpen() {
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_UnblockingOpen);
-
-  mDirectoryLock = nullptr;
-
-  mState = State_Completed;
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -7,63 +7,49 @@
 #ifndef mozilla_dom_indexeddatabasemanager_h__
 #define mozilla_dom_indexeddatabasemanager_h__
 
 #include "js/TypeDecls.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/Mutex.h"
 #include "nsClassHashtable.h"
-#include "nsCOMPtr.h"
 #include "nsHashKeys.h"
-#include "nsINamed.h"
-#include "nsITimer.h"
-
-class nsIEventTarget;
 
 namespace mozilla {
 
 class EventChainPostVisitor;
 
 namespace dom {
 
 class IDBFactory;
 
-namespace quota {
-
-class QuotaManager;
-
-}  // namespace quota
-
 namespace indexedDB {
 
 class BackgroundUtilsChild;
 class FileManager;
 class FileManagerInfo;
 
 }  // namespace indexedDB
 
-class IndexedDatabaseManager final : public nsITimerCallback, public nsINamed {
+class IndexedDatabaseManager final {
   typedef mozilla::dom::quota::PersistenceType PersistenceType;
-  typedef mozilla::dom::quota::QuotaManager QuotaManager;
   typedef mozilla::dom::indexedDB::FileManager FileManager;
   typedef mozilla::dom::indexedDB::FileManagerInfo FileManagerInfo;
 
  public:
   enum LoggingMode {
     Logging_Disabled = 0,
     Logging_Concise,
     Logging_Detailed,
     Logging_ConciseProfilerMarks,
     Logging_DetailedProfilerMarks
   };
 
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSITIMERCALLBACK
-  NS_DECL_NSINAMED
+  NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(IndexedDatabaseManager, Destroy())
 
   // Returns a non-owning reference.
   static IndexedDatabaseManager* GetOrCreate();
 
   // Returns a non-owning reference.
   static IndexedDatabaseManager* Get();
 
   static bool IsClosed();
@@ -106,37 +92,31 @@ class IndexedDatabaseManager final : pub
   static bool IsFileHandleEnabled();
 
   static uint32_t DataThreshold();
 
   static uint32_t MaxSerializedMsgSize();
 
   void ClearBackgroundActor();
 
-  void NoteLiveQuotaManager(QuotaManager* aQuotaManager);
-
-  void NoteShuttingDownQuotaManager();
-
   already_AddRefed<FileManager> GetFileManager(PersistenceType aPersistenceType,
                                                const nsACString& aOrigin,
                                                const nsAString& aDatabaseName);
 
   void AddFileManager(FileManager* aFileManager);
 
   void InvalidateAllFileManagers();
 
   void InvalidateFileManagers(PersistenceType aPersistenceType,
                               const nsACString& aOrigin);
 
   void InvalidateFileManager(PersistenceType aPersistenceType,
                              const nsACString& aOrigin,
                              const nsAString& aDatabaseName);
 
-  nsresult AsyncDeleteFile(FileManager* aFileManager, int64_t aFileId);
-
   // Don't call this method in real code, it blocks the main thread!
   // It is intended to be used by mochitests to test correctness of the special
   // reference counting of stored blobs/files.
   nsresult BlockAndGetFileReferences(PersistenceType aPersistenceType,
                                      const nsACString& aOrigin,
                                      const nsAString& aDatabaseName,
                                      int64_t aFileId, int32_t* aRefCnt,
                                      int32_t* aDBRefCnt, int32_t* aSliceRefCnt,
@@ -166,20 +146,16 @@ class IndexedDatabaseManager final : pub
 
   nsresult Init();
 
   void Destroy();
 
   static void LoggingModePrefChangedCallback(const char* aPrefName,
                                              void* aClosure);
 
-  nsCOMPtr<nsIEventTarget> mBackgroundThread;
-
-  nsCOMPtr<nsITimer> mDeleteTimer;
-
   // Maintains a list of all file managers per origin. This list isn't
   // protected by any mutex but it is only ever touched on the IO thread.
   nsClassHashtable<nsCStringHashKey, FileManagerInfo> mFileManagerInfos;
 
   nsClassHashtable<nsRefPtrHashKey<FileManager>, nsTArray<int64_t>>
       mPendingDeleteInfos;
 
   // Lock protecting FileManager.mFileInfos.
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5315,18 +5315,18 @@ nsresult ContentParent::AboutToLoadHttpF
     nsCOMPtr<nsILocalStorageManager> lsm =
         do_GetService("@mozilla.org/dom/localStorage-manager;1");
     if (NS_WARN_IF(!lsm)) {
       return NS_ERROR_FAILURE;
     }
 
     nsCOMPtr<nsISupports> dummy;
     rv = lsm->Preload(principal, nullptr, getter_AddRefs(dummy));
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to preload local storage!");
     }
   }
 
   return NS_OK;
 }
 
 nsresult ContentParent::TransmitPermissionsForPrincipal(
     nsIPrincipal* aPrincipal) {
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -12,16 +12,17 @@
 #include "mozIStorageFunction.h"
 #include "mozIStorageService.h"
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ClientManagerService.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
 #include "mozilla/dom/PBackgroundLSObserverParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
 #include "mozilla/dom/PBackgroundLSSimpleRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSnapshotParent.h"
 #include "mozilla/dom/StorageDBUpdater.h"
 #include "mozilla/dom/StorageUtils.h"
@@ -176,19 +177,25 @@ static const uint32_t kUsageFileCookie =
  * Note that flushing happens downstream of Snapshot checkpointing and its
  * batch mechanism which helps avoid wasteful IPC in the case of silly content
  * code.
  */
 const uint32_t kFlushTimeoutMs = 5000;
 
 const char kPrivateBrowsingObserverTopic[] = "last-pb-context-exited";
 
+const uint32_t kDefaultNextGen = false;
 const uint32_t kDefaultOriginLimitKB = 5 * 1024;
 const uint32_t kDefaultShadowWrites = true;
 const uint32_t kDefaultSnapshotPrefill = 4096;
+const uint32_t kDefaultClientValidation = true;
+/**
+ *
+ */
+const char kNextGenPref[] = "dom.storage.next_gen";
 /**
  * LocalStorage data limit as determined by summing up the lengths of all string
  * keys and values.  This is consistent with the legacy implementation and other
  * browser engines.  This value should really only ever change in unit testing
  * where being able to lower it makes it easier for us to test certain edge
  * cases.
  */
 const char kDefaultQuotaPref[] = "dom.storage.default_quota";
@@ -204,16 +211,18 @@ const char kShadowWritesPref[] = "dom.st
 /**
  * Byte budget for sending data down to the LSSnapshot instance when it is first
  * created.  If there is less data than this (measured by tallying the string
  * length of the keys and values), all data is sent, otherwise partial data is
  * sent.  See `Snapshot`.
  */
 const char kSnapshotPrefillPref[] = "dom.storage.snapshot_prefill";
 
+const char kClientValidationPref[] = "dom.storage.client_validation";
+
 /**
  * The amount of time a PreparedDatastore instance should stick around after a
  * preload is triggered in order to give time for the page to use LocalStorage
  * without triggering worst-case synchronous jank.
  */
 const uint32_t kPreparedDatastoreTimeoutMs = 20000;
 
 /**
@@ -2223,29 +2232,30 @@ class PrepareDatastoreOp : public LSRequ
   RefPtr<PrepareDatastoreOp> mDelayedOp;
   RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Connection> mConnection;
   RefPtr<Datastore> mDatastore;
   nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
   LoadDataOp* mLoadDataOp;
   nsDataHashtable<nsStringHashKey, nsString> mValues;
   nsTArray<LSItemInfo> mOrderedItems;
-  const LSRequestPrepareDatastoreParams mParams;
+  const LSRequestCommonParams mParams;
   Maybe<ContentParentId> mContentParentId;
   nsCString mSuffix;
   nsCString mGroup;
   nsCString mMainThreadOrigin;
   nsCString mOrigin;
   nsString mDirectoryPath;
   nsString mDatabaseFilePath;
   uint32_t mPrivateBrowsingId;
   int64_t mUsage;
   int64_t mSizeOfKeys;
   int64_t mSizeOfItems;
   NestedState mNestedState;
+  const bool mCreateIfNotExists;
   bool mDatabaseNotAvailable;
   bool mRequestedDirectoryLock;
   bool mInvalidated;
 
 #ifdef DEBUG
   int64_t mDEBUGUsage;
 #endif
 
@@ -2485,19 +2495,20 @@ class ArchivedOriginScope {
 
   struct Null {};
 
   using DataType = Variant<Origin, Pattern, Prefix, Null>;
 
   DataType mData;
 
  public:
-  static ArchivedOriginScope* CreateFromOrigin(nsIPrincipal* aPrincipal);
-
-  static ArchivedOriginScope* CreateFromPrefix(nsIPrincipal* aPrincipal);
+  static ArchivedOriginScope* CreateFromOrigin(
+      const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey);
+
+  static ArchivedOriginScope* CreateFromPrefix(const nsACString& aOriginKey);
 
   static ArchivedOriginScope* CreateFromPattern(
       const OriginAttributesPattern& aPattern);
 
   static ArchivedOriginScope* CreateFromNull();
 
   bool IsOrigin() const { return mData.is<Origin>(); }
 
@@ -2542,47 +2553,16 @@ class ArchivedOriginScope {
 
   explicit ArchivedOriginScope(const Pattern&& aPattern) : mData(aPattern) {}
 
   explicit ArchivedOriginScope(const Prefix&& aPrefix) : mData(aPrefix) {}
 
   explicit ArchivedOriginScope(const Null&& aNull) : mData(aNull) {}
 };
 
-class ArchivedOriginScopeHelper : public Runnable {
-  Monitor mMonitor;
-  const OriginAttributes mAttrs;
-  const nsCString mSpec;
-  nsAutoPtr<ArchivedOriginScope> mArchivedOriginScope;
-  nsresult mMainThreadResultCode;
-  bool mWaiting;
-  bool mPrefix;
-
- public:
-  ArchivedOriginScopeHelper(const nsACString& aSpec,
-                            const OriginAttributes& aAttrs, bool aPrefix)
-      : Runnable("dom::localstorage::ArchivedOriginScopeHelper"),
-        mMonitor("ArchivedOriginScopeHelper::mMonitor"),
-        mAttrs(aAttrs),
-        mSpec(aSpec),
-        mMainThreadResultCode(NS_OK),
-        mWaiting(true),
-        mPrefix(aPrefix) {
-    AssertIsOnIOThread();
-  }
-
-  nsresult BlockAndReturnArchivedOriginScope(
-      nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope);
-
- private:
-  nsresult RunOnMainThread();
-
-  NS_DECL_NSIRUNNABLE
-};
-
 class QuotaClient final : public mozilla::dom::quota::Client {
   class Observer;
   class MatchFunction;
 
   static QuotaClient* sInstance;
 
   Mutex mShadowDatabaseMutex;
   bool mShutdownRequested;
@@ -2738,19 +2718,21 @@ typedef nsRefPtrHashtable<nsUint64HashKe
 
 StaticAutoPtr<PreparedObserverHashtable> gPreparedObsevers;
 
 typedef nsClassHashtable<nsCStringHashKey, nsTArray<Observer*>>
     ObserverHashtable;
 
 StaticAutoPtr<ObserverHashtable> gObservers;
 
+Atomic<bool> gNextGen(kDefaultNextGen);
 Atomic<uint32_t, Relaxed> gOriginLimitKB(kDefaultOriginLimitKB);
 Atomic<bool> gShadowWrites(kDefaultShadowWrites);
 Atomic<int32_t, Relaxed> gSnapshotPrefill(kDefaultSnapshotPrefill);
+Atomic<bool> gClientValidation(kDefaultClientValidation);
 
 typedef nsDataHashtable<nsCStringHashKey, int64_t> UsageHashtable;
 
 // Can only be touched on the Quota Manager I/O thread.
 StaticAutoPtr<UsageHashtable> gUsages;
 
 StaticAutoPtr<ArchivedOriginHashtable> gArchivedOrigins;
 
@@ -2927,16 +2909,25 @@ void SnapshotPrefillPrefChangedCallback(
   // The magic -1 is for use only by tests.
   if (snapshotPrefill == -1) {
     snapshotPrefill = INT32_MAX;
   }
 
   gSnapshotPrefill = snapshotPrefill;
 }
 
+void ClientValidationPrefChangedCallback(const char* aPrefName,
+                                         void* aClosure) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aPrefName, kClientValidationPref));
+  MOZ_ASSERT(!aClosure);
+
+  gClientValidation = Preferences::GetBool(aPrefName, kDefaultClientValidation);
+}
+
 }  // namespace
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
 void InitializeLocalStorage() {
   MOZ_ASSERT(XRE_IsParentProcess());
@@ -2950,32 +2941,42 @@ void InitializeLocalStorage() {
       NS_WARNING("Failed to get storage service!");
     }
   }
 
   if (NS_FAILED(QuotaClient::Initialize())) {
     NS_WARNING("Failed to initialize quota client!");
   }
 
+  if (NS_FAILED(Preferences::AddAtomicBoolVarCache(&gNextGen, kNextGenPref,
+                                                   kDefaultNextGen))) {
+    NS_WARNING("Unable to respond to next gen pref changes!");
+  }
+
   if (NS_FAILED(Preferences::AddAtomicUintVarCache(
           &gOriginLimitKB, kDefaultQuotaPref, kDefaultOriginLimitKB))) {
     NS_WARNING("Unable to respond to default quota pref changes!");
   }
 
   Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback,
                                        kShadowWritesPref);
 
   Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback,
                                        kSnapshotPrefillPref);
 
+  Preferences::RegisterCallbackAndCall(ClientValidationPrefChangedCallback,
+                                       kClientValidationPref);
+
 #ifdef DEBUG
   gLocalStorageInitialized = true;
 #endif
 }
 
+bool GetCurrentNextGenPrefValue() { return gNextGen; }
+
 PBackgroundLSDatabaseParent* AllocPBackgroundLSDatabaseParent(
     const PrincipalInfo& aPrincipalInfo, const uint32_t& aPrivateBrowsingId,
     const uint64_t& aDatastoreId) {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
@@ -3106,42 +3107,166 @@ bool DeallocPBackgroundLSObserverParent(
   MOZ_ASSERT(aActor);
 
   // Transfer ownership back from IPDL.
   RefPtr<Observer> actor = dont_AddRef(static_cast<Observer*>(aActor));
 
   return true;
 }
 
+bool VerifyPrincipalInfo(const Maybe<ContentParentId>& aContentParentId,
+                         const PrincipalInfo& aPrincipalInfo,
+                         const Maybe<nsID>& aClientId) {
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (aClientId.isSome() && gClientValidation) {
+    RefPtr<ClientManagerService> svc = ClientManagerService::GetInstance();
+    if (svc &&
+        !svc->HasWindow(aContentParentId, aPrincipalInfo, aClientId.ref())) {
+      ASSERT_UNLESS_FUZZING();
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool VerifyOriginKey(const nsACString& aOriginKey,
+                     const PrincipalInfo& aPrincipalInfo) {
+  AssertIsOnBackgroundThread();
+
+  nsCString originAttrSuffix;
+  nsCString originKey;
+  nsresult rv = GenerateOriginKey2(aPrincipalInfo, originAttrSuffix, originKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (NS_WARN_IF(originKey != aOriginKey)) {
+    LS_WARNING("originKey (%s) doesn't match passed one (%s)!", originKey.get(),
+               nsCString(aOriginKey).get());
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  return true;
+}
+
+bool VerifyRequestParams(const Maybe<ContentParentId>& aContentParentId,
+                         const LSRequestParams& aParams) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
+
+  switch (aParams.type()) {
+    case LSRequestParams::TLSRequestPreloadDatastoreParams: {
+      const LSRequestCommonParams& params =
+          aParams.get_LSRequestPreloadDatastoreParams().commonParams();
+
+      if (NS_WARN_IF(!VerifyPrincipalInfo(aContentParentId,
+                                          params.principalInfo(), Nothing()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      if (NS_WARN_IF(
+              !VerifyOriginKey(params.originKey(), params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+      break;
+    }
+
+    case LSRequestParams::TLSRequestPrepareDatastoreParams: {
+      const LSRequestPrepareDatastoreParams& params =
+          aParams.get_LSRequestPrepareDatastoreParams();
+
+      const LSRequestCommonParams& commonParams = params.commonParams();
+
+      if (NS_WARN_IF(!VerifyPrincipalInfo(aContentParentId,
+                                          commonParams.principalInfo(),
+                                          params.clientId()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      if (NS_WARN_IF(!VerifyOriginKey(commonParams.originKey(),
+                                      commonParams.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+      break;
+    }
+
+    case LSRequestParams::TLSRequestPrepareObserverParams: {
+      const LSRequestPrepareObserverParams& params =
+          aParams.get_LSRequestPrepareObserverParams();
+
+      if (NS_WARN_IF(!VerifyPrincipalInfo(aContentParentId,
+                                          params.principalInfo(),
+                                          params.clientId()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
 PBackgroundLSRequestParent* AllocPBackgroundLSRequestParent(
     PBackgroundParent* aBackgroundActor, const LSRequestParams& aParams) {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != LSRequestParams::T__None);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
 
+#ifdef DEBUG
+  // Always verify parameters in DEBUG builds!
+  bool trustParams = false;
+#else
+  bool trustParams = !BackgroundParent::IsOtherProcessActor(aBackgroundActor);
+#endif
+
+  Maybe<ContentParentId> contentParentId;
+
+  uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
+  if (childID) {
+    contentParentId = Some(ContentParentId(childID));
+  }
+
+  if (!trustParams &&
+      NS_WARN_IF(!VerifyRequestParams(contentParentId, aParams))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
   // If we're in the same process as the actor, we need to get the target event
   // queue from the current RequestHelper.
   nsCOMPtr<nsIEventTarget> mainEventTarget;
   if (!BackgroundParent::IsOtherProcessActor(aBackgroundActor)) {
     mainEventTarget = LSObject::GetSyncLoopEventTarget();
   }
 
   RefPtr<LSRequestBase> actor;
 
   switch (aParams.type()) {
+    case LSRequestParams::TLSRequestPreloadDatastoreParams:
     case LSRequestParams::TLSRequestPrepareDatastoreParams: {
-      Maybe<ContentParentId> contentParentId;
-
-      uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
-      if (childID) {
-        contentParentId = Some(ContentParentId(childID));
-      }
-
       RefPtr<PrepareDatastoreOp> prepareDatastoreOp =
           new PrepareDatastoreOp(mainEventTarget, aParams, contentParentId);
 
       if (!gPrepareDatastoreOps) {
         gPrepareDatastoreOps = new PrepareDatastoreOpArray();
       }
       gPrepareDatastoreOps->AppendElement(prepareDatastoreOp);
 
@@ -3188,24 +3313,72 @@ bool DeallocPBackgroundLSRequestParent(P
 
   // Transfer ownership back from IPDL.
   RefPtr<LSRequestBase> actor =
       dont_AddRef(static_cast<LSRequestBase*>(aActor));
 
   return true;
 }
 
+bool VerifyRequestParams(const Maybe<ContentParentId>& aContentParentId,
+                         const LSSimpleRequestParams& aParams) {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
+
+  switch (aParams.type()) {
+    case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
+      const LSSimpleRequestPreloadedParams& params =
+          aParams.get_LSSimpleRequestPreloadedParams();
+
+      if (NS_WARN_IF(!VerifyPrincipalInfo(aContentParentId,
+                                          params.principalInfo(),
+                                          Nothing()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
 PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent(
-    const LSSimpleRequestParams& aParams) {
+    PBackgroundParent* aBackgroundActor, const LSSimpleRequestParams& aParams) {
   AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != LSSimpleRequestParams::T__None);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
 
+#ifdef DEBUG
+  // Always verify parameters in DEBUG builds!
+  bool trustParams = false;
+#else
+  bool trustParams = !BackgroundParent::IsOtherProcessActor(aBackgroundActor);
+#endif
+
+  if (!trustParams) {
+    Maybe<ContentParentId> contentParentId;
+
+    uint64_t childID = BackgroundParent::GetChildID(aBackgroundActor);
+    if (childID) {
+      contentParentId = Some(ContentParentId(childID));
+    }
+
+    if (NS_WARN_IF(!VerifyRequestParams(contentParentId, aParams))) {
+      ASSERT_UNLESS_FUZZING();
+      return nullptr;
+    }
+  }
+
   RefPtr<LSSimpleRequestBase> actor;
 
   switch (aParams.type()) {
     case LSSimpleRequestParams::TLSSimpleRequestPreloadedParams: {
       RefPtr<PreloadedOp> preloadedOp = new PreloadedOp(aParams);
 
       actor = std::move(preloadedOp);
 
@@ -5442,21 +5615,17 @@ LSRequestBase::~LSRequestBase() {
                 mState == State::Initial || mState == State::Completed);
 }
 
 void LSRequestBase::Dispatch() {
   AssertIsOnOwningThread();
 
   mState = State::Opening;
 
-  if (mMainEventTarget) {
-    MOZ_ALWAYS_SUCCEEDS(mMainEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
-  } else {
-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-  }
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
 }
 
 nsresult LSRequestBase::NestedRun() { return NS_OK; }
 
 void LSRequestBase::SendReadyMessage() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingReadyMessage);
 
@@ -5582,81 +5751,67 @@ mozilla::ipc::IPCResult LSRequestBase::R
  ******************************************************************************/
 
 PrepareDatastoreOp::PrepareDatastoreOp(
     nsIEventTarget* aMainEventTarget, const LSRequestParams& aParams,
     const Maybe<ContentParentId>& aContentParentId)
     : LSRequestBase(aMainEventTarget),
       mMainEventTarget(aMainEventTarget),
       mLoadDataOp(nullptr),
-      mParams(aParams.get_LSRequestPrepareDatastoreParams()),
+      mParams(
+          aParams.type() == LSRequestParams::TLSRequestPreloadDatastoreParams
+              ? aParams.get_LSRequestPreloadDatastoreParams().commonParams()
+              : aParams.get_LSRequestPrepareDatastoreParams().commonParams()),
       mContentParentId(aContentParentId),
       mPrivateBrowsingId(0),
       mUsage(0),
       mSizeOfKeys(0),
       mSizeOfItems(0),
       mNestedState(NestedState::BeforeNesting),
+      mCreateIfNotExists(aParams.type() ==
+                         LSRequestParams::TLSRequestPrepareDatastoreParams),
       mDatabaseNotAvailable(false),
       mRequestedDirectoryLock(false),
       mInvalidated(false)
 #ifdef DEBUG
       ,
       mDEBUGUsage(0)
 #endif
 {
-  MOZ_ASSERT(aParams.type() ==
-             LSRequestParams::TLSRequestPrepareDatastoreParams);
+  MOZ_ASSERT(
+      aParams.type() == LSRequestParams::TLSRequestPreloadDatastoreParams ||
+      aParams.type() == LSRequestParams::TLSRequestPrepareDatastoreParams);
 }
 
 PrepareDatastoreOp::~PrepareDatastoreOp() {
   MOZ_ASSERT(!mDirectoryLock);
   MOZ_ASSERT_IF(MayProceedOnNonOwningThread(),
                 mState == State::Initial || mState == State::Completed);
   MOZ_ASSERT(!mLoadDataOp);
 }
 
 nsresult PrepareDatastoreOp::Open() {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::Opening);
   MOZ_ASSERT(mNestedState == NestedState::BeforeNesting);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   const PrincipalInfo& principalInfo = mParams.principalInfo();
 
   if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin);
   } else {
     MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
 
-    nsresult rv;
-    nsCOMPtr<nsIPrincipal> principal =
-        PrincipalInfoToPrincipal(principalInfo, &rv);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup,
-                                            &mMainThreadOrigin);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = principal->GetPrivateBrowsingId(&mPrivateBrowsingId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(principal);
-    if (NS_WARN_IF(!mArchivedOriginScope)) {
-      return NS_ERROR_FAILURE;
-    }
+    QuotaManager::GetInfoFromValidatedPrincipalInfo(
+        principalInfo, &mSuffix, &mGroup, &mMainThreadOrigin);
   }
 
   mState = State::Nesting;
   mNestedState = NestedState::CheckExistingOperations;
 
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
 
   return NS_OK;
@@ -5668,26 +5823,49 @@ nsresult PrepareDatastoreOp::CheckExisti
   MOZ_ASSERT(mNestedState == NestedState::CheckExistingOperations);
   MOZ_ASSERT(gPrepareDatastoreOps);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
     return NS_ERROR_FAILURE;
   }
 
+  const PrincipalInfo& principalInfo = mParams.principalInfo();
+
+  nsCString originAttrSuffix;
+  uint32_t privateBrowsingId;
+
+  if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
+    privateBrowsingId = 0;
+  } else {
+    MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
+
+    const ContentPrincipalInfo& info = principalInfo.get_ContentPrincipalInfo();
+    const OriginAttributes& attrs = info.attrs();
+    attrs.CreateSuffix(originAttrSuffix);
+
+    privateBrowsingId = attrs.mPrivateBrowsingId;
+  }
+
+  mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(
+      originAttrSuffix, mParams.originKey());
+  MOZ_ASSERT(mArchivedOriginScope);
+
   // Normally it's safe to access member variables without a mutex because even
   // though we hop between threads, the variables are never accessed by multiple
   // threads at the same time.
   // However, the methods OriginIsKnown and Origin can be called at any time.
   // So we have to make sure the member variable is set on the same thread as
   // those methods are called.
   mOrigin = mMainThreadOrigin;
 
   MOZ_ASSERT(!mOrigin.IsEmpty());
 
+  mPrivateBrowsingId = privateBrowsingId;
+
   mNestedState = NestedState::CheckClosingDatastore;
 
   // See if this PrepareDatastoreOp needs to wait.
   bool foundThis = false;
   for (uint32_t index = gPrepareDatastoreOps->Length(); index > 0; index--) {
     PrepareDatastoreOp* existingOp = (*gPrepareDatastoreOps)[index - 1];
 
     if (existingOp == this) {
@@ -5901,17 +6079,17 @@ nsresult PrepareDatastoreOp::DatabaseWor
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(gArchivedOrigins);
   }
 
   bool hasDataForMigration = mArchivedOriginScope->HasMatches(gArchivedOrigins);
 
-  bool createIfNotExists = mParams.createIfNotExists() || hasDataForMigration;
+  bool createIfNotExists = mCreateIfNotExists || hasDataForMigration;
 
   nsCOMPtr<nsIFile> directoryEntry;
   rv = quotaManager->EnsureOriginIsInitialized(
       PERSISTENCE_TYPE_DEFAULT, mSuffix, mGroup, mOrigin, createIfNotExists,
       getter_AddRefs(directoryEntry));
   if (rv == NS_ERROR_NOT_AVAILABLE) {
     return DatabaseNotAvailable();
   }
@@ -6347,22 +6525,21 @@ nsresult PrepareDatastoreOp::NestedRun()
 }
 
 void PrepareDatastoreOp::GetResponse(LSRequestResponse& aResponse) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
   MOZ_ASSERT(NS_SUCCEEDED(ResultCode()));
 
   if (mDatabaseNotAvailable) {
-    MOZ_ASSERT(!mParams.createIfNotExists());
-
-    LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
-    prepareDatastoreResponse.datastoreId() = null_t();
-
-    aResponse = prepareDatastoreResponse;
+    MOZ_ASSERT(!mCreateIfNotExists);
+
+    LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
+
+    aResponse = preloadDatastoreResponse;
 
     return;
   }
 
   if (!mDatastore) {
     MOZ_ASSERT(mUsage == mDEBUGUsage);
 
     RefPtr<QuotaObject> quotaObject;
@@ -6386,33 +6563,39 @@ void PrepareDatastoreOp::GetResponse(LSR
     MOZ_ASSERT(!gDatastores->Get(mOrigin));
     gDatastores->Put(mOrigin, mDatastore);
   }
 
   uint64_t datastoreId = ++gLastDatastoreId;
 
   nsAutoPtr<PreparedDatastore> preparedDatastore(
       new PreparedDatastore(mDatastore, mContentParentId, mOrigin, datastoreId,
-                            /* aForPreload */ !mParams.createIfNotExists()));
+                            /* aForPreload */ !mCreateIfNotExists));
 
   if (!gPreparedDatastores) {
     gPreparedDatastores = new PreparedDatastoreHashtable();
   }
   gPreparedDatastores->Put(datastoreId, preparedDatastore);
 
   if (mInvalidated) {
     preparedDatastore->Invalidate();
   }
 
   preparedDatastore.forget();
 
-  LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
-  prepareDatastoreResponse.datastoreId() = datastoreId;
-
-  aResponse = prepareDatastoreResponse;
+  if (mCreateIfNotExists) {
+    LSRequestPrepareDatastoreResponse prepareDatastoreResponse;
+    prepareDatastoreResponse.datastoreId() = datastoreId;
+
+    aResponse = prepareDatastoreResponse;
+  } else {
+    LSRequestPreloadDatastoreResponse preloadDatastoreResponse;
+
+    aResponse = preloadDatastoreResponse;
+  }
 }
 
 void PrepareDatastoreOp::Cleanup() {
   AssertIsOnOwningThread();
 
   if (mDatastore) {
     MOZ_ASSERT(!mDirectoryLock);
     MOZ_ASSERT(!mConnection);
@@ -6626,43 +6809,33 @@ PrepareObserverOp::PrepareObserverOp(nsI
                                      const LSRequestParams& aParams)
     : LSRequestBase(aMainEventTarget),
       mParams(aParams.get_LSRequestPrepareObserverParams()) {
   MOZ_ASSERT(aParams.type() ==
              LSRequestParams::TLSRequestPrepareObserverParams);
 }
 
 nsresult PrepareObserverOp::Open() {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::Opening);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   const PrincipalInfo& principalInfo = mParams.principalInfo();
 
   if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin);
   } else {
     MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
 
-    nsresult rv;
-    nsCOMPtr<nsIPrincipal> principal =
-        PrincipalInfoToPrincipal(principalInfo, &rv);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr,
-                                            &mOrigin);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    QuotaManager::GetInfoFromValidatedPrincipalInfo(principalInfo, nullptr,
+                                                    nullptr, &mOrigin);
   }
 
   mState = State::SendingReadyMessage;
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
 
   return NS_OK;
 }
 
@@ -6698,17 +6871,17 @@ LSSimpleRequestBase::~LSSimpleRequestBas
                 mState == State::Initial || mState == State::Completed);
 }
 
 void LSSimpleRequestBase::Dispatch() {
   AssertIsOnOwningThread();
 
   mState = State::Opening;
 
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
 }
 
 void LSSimpleRequestBase::SendResults() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::SendingResults);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceed()) {
@@ -6777,43 +6950,33 @@ void LSSimpleRequestBase::ActorDestroy(A
 
 PreloadedOp::PreloadedOp(const LSSimpleRequestParams& aParams)
     : mParams(aParams.get_LSSimpleRequestPreloadedParams()) {
   MOZ_ASSERT(aParams.type() ==
              LSSimpleRequestParams::TLSSimpleRequestPreloadedParams);
 }
 
 nsresult PreloadedOp::Open() {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::Opening);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !MayProceedOnNonOwningThread()) {
     return NS_ERROR_FAILURE;
   }
 
   const PrincipalInfo& principalInfo = mParams.principalInfo();
 
   if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
     QuotaManager::GetInfoForChrome(nullptr, nullptr, &mOrigin);
   } else {
     MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
 
-    nsresult rv;
-    nsCOMPtr<nsIPrincipal> principal =
-        PrincipalInfoToPrincipal(principalInfo, &rv);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr,
-                                            &mOrigin);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
+    QuotaManager::GetInfoFromValidatedPrincipalInfo(principalInfo, nullptr,
+                                                    nullptr, &mOrigin);
   }
 
   mState = State::SendingResults;
   MOZ_ALWAYS_SUCCEEDS(OwningEventTarget()->Dispatch(this, NS_DISPATCH_NORMAL));
 
   return NS_OK;
 }
 
@@ -6838,45 +7001,25 @@ void PreloadedOp::GetResponse(LSSimpleRe
 }
 
 /*******************************************************************************
  * ArchivedOriginScope
  ******************************************************************************/
 
 // static
 ArchivedOriginScope* ArchivedOriginScope::CreateFromOrigin(
-    nsIPrincipal* aPrincipal) {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aPrincipal);
-
-  nsCString originAttrSuffix;
-  nsCString originKey;
-  nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return nullptr;
-  }
-
+    const nsACString& aOriginAttrSuffix, const nsACString& aOriginKey) {
   return new ArchivedOriginScope(
-      std::move(Origin(originAttrSuffix, originKey)));
+      std::move(Origin(aOriginAttrSuffix, aOriginKey)));
 }
 
 // static
 ArchivedOriginScope* ArchivedOriginScope::CreateFromPrefix(
-    nsIPrincipal* aPrincipal) {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aPrincipal);
-
-  nsCString originAttrSuffix;
-  nsCString originKey;
-  nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return nullptr;
-  }
-
-  return new ArchivedOriginScope(std::move(Prefix(originKey)));
+    const nsACString& aOriginKey) {
+  return new ArchivedOriginScope(std::move(Prefix(aOriginKey)));
 }
 
 // static
 ArchivedOriginScope* ArchivedOriginScope::CreateFromPattern(
     const OriginAttributesPattern& aPattern) {
   return new ArchivedOriginScope(std::move(Pattern(aPattern)));
 }
 
@@ -7062,84 +7205,16 @@ void ArchivedOriginScope::RemoveMatches(
 
     void match(const Null& aNull) { mHashtable->Clear(); }
   };
 
   mData.match(Matcher(aHashtable));
 }
 
 /*******************************************************************************
- * ArchivedOriginScopeHelper
- ******************************************************************************/
-
-nsresult ArchivedOriginScopeHelper::BlockAndReturnArchivedOriginScope(
-    nsAutoPtr<ArchivedOriginScope>& aArchivedOriginScope) {
-  AssertIsOnIOThread();
-
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-
-  mozilla::MonitorAutoLock lock(mMonitor);
-  while (mWaiting) {
-    lock.Wait();
-  }
-
-  if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
-    return mMainThreadResultCode;
-  }
-
-  aArchivedOriginScope = std::move(mArchivedOriginScope);
-  return NS_OK;
-}
-
-nsresult ArchivedOriginScopeHelper::RunOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = NS_NewURI(getter_AddRefs(uri), mSpec);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsCOMPtr<nsIPrincipal> principal =
-      BasePrincipal::CreateCodebasePrincipal(uri, mAttrs);
-  if (NS_WARN_IF(!principal)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (mPrefix) {
-    mArchivedOriginScope = ArchivedOriginScope::CreateFromPrefix(principal);
-  } else {
-    mArchivedOriginScope = ArchivedOriginScope::CreateFromOrigin(principal);
-  }
-  if (NS_WARN_IF(!mArchivedOriginScope)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-ArchivedOriginScopeHelper::Run() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsresult rv = RunOnMainThread();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mMainThreadResultCode = rv;
-  }
-
-  mozilla::MonitorAutoLock lock(mMonitor);
-  MOZ_ASSERT(mWaiting);
-
-  mWaiting = false;
-  lock.Notify();
-
-  return NS_OK;
-}
-
-/*******************************************************************************
  * QuotaClient
  ******************************************************************************/
 
 QuotaClient* QuotaClient::sInstance = nullptr;
 
 QuotaClient::QuotaClient()
     : mShadowDatabaseMutex("LocalStorage mShadowDatabaseMutex"),
       mShutdownRequested(false) {
@@ -7767,38 +7842,53 @@ nsresult QuotaClient::CreateArchivedOrig
   if (aOriginScope.IsOrigin()) {
     nsCString spec;
     OriginAttributes attrs;
     if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOrigin(), spec,
                                               &attrs))) {
       return NS_ERROR_FAILURE;
     }
 
-    RefPtr<ArchivedOriginScopeHelper> helper =
-        new ArchivedOriginScopeHelper(spec, attrs, /* aPrefix */ false);
-
-    rv = helper->BlockAndReturnArchivedOriginScope(archivedOriginScope);
+    ContentPrincipalInfo contentPrincipalInfo;
+    contentPrincipalInfo.attrs() = attrs;
+    contentPrincipalInfo.spec() = spec;
+
+    PrincipalInfo principalInfo(contentPrincipalInfo);
+
+    nsCString originAttrSuffix;
+    nsCString originKey;
+    rv = GenerateOriginKey2(principalInfo, originAttrSuffix, originKey);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+
+    archivedOriginScope =
+        ArchivedOriginScope::CreateFromOrigin(originAttrSuffix, originKey);
   } else if (aOriginScope.IsPrefix()) {
     nsCString spec;
     OriginAttributes attrs;
     if (NS_WARN_IF(!QuotaManager::ParseOrigin(aOriginScope.GetOriginNoSuffix(),
                                               spec, &attrs))) {
       return NS_ERROR_FAILURE;
     }
 
-    RefPtr<ArchivedOriginScopeHelper> helper =
-        new ArchivedOriginScopeHelper(spec, attrs, /* aPrefix */ true);
-
-    rv = helper->BlockAndReturnArchivedOriginScope(archivedOriginScope);
+    ContentPrincipalInfo contentPrincipalInfo;
+    contentPrincipalInfo.attrs() = attrs;
+    contentPrincipalInfo.spec() = spec;
+
+    PrincipalInfo principalInfo(contentPrincipalInfo);
+
+    nsCString originAttrSuffix;
+    nsCString originKey;
+    rv = GenerateOriginKey2(principalInfo, originAttrSuffix, originKey);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
+
+    archivedOriginScope = ArchivedOriginScope::CreateFromPrefix(originKey);
   } else if (aOriginScope.IsPattern()) {
     archivedOriginScope =
         ArchivedOriginScope::CreateFromPattern(aOriginScope.GetPattern());
   } else {
     MOZ_ASSERT(aOriginScope.IsNull());
 
     archivedOriginScope = ArchivedOriginScope::CreateFromNull();
   }
--- a/dom/localstorage/ActorsParent.h
+++ b/dom/localstorage/ActorsParent.h
@@ -28,16 +28,18 @@ class PBackgroundLSSimpleRequestParent;
 namespace quota {
 
 class Client;
 
 }  // namespace quota
 
 void InitializeLocalStorage();
 
+bool GetCurrentNextGenPrefValue();
+
 PBackgroundLSDatabaseParent* AllocPBackgroundLSDatabaseParent(
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
     const uint32_t& aPrivateBrowsingId, const uint64_t& aDatastoreId);
 
 bool RecvPBackgroundLSDatabaseConstructor(
     PBackgroundLSDatabaseParent* aActor,
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
     const uint32_t& aPrivateBrowsingId, const uint64_t& aDatastoreId);
@@ -57,16 +59,17 @@ PBackgroundLSRequestParent* AllocPBackgr
     const LSRequestParams& aParams);
 
 bool RecvPBackgroundLSRequestConstructor(PBackgroundLSRequestParent* aActor,
                                          const LSRequestParams& aParams);
 
 bool DeallocPBackgroundLSRequestParent(PBackgroundLSRequestParent* aActor);
 
 PBackgroundLSSimpleRequestParent* AllocPBackgroundLSSimpleRequestParent(
+    mozilla::ipc::PBackgroundParent* aBackgroundActor,
     const LSSimpleRequestParams& aParams);
 
 bool RecvPBackgroundLSSimpleRequestConstructor(
     PBackgroundLSSimpleRequestParent* aActor,
     const LSSimpleRequestParams& aParams);
 
 bool DeallocPBackgroundLSSimpleRequestParent(
     PBackgroundLSSimpleRequestParent* aActor);
--- a/dom/localstorage/LSDatabase.cpp
+++ b/dom/localstorage/LSDatabase.cpp
@@ -4,34 +4,61 @@
  * 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 "LSDatabase.h"
 
 namespace mozilla {
 namespace dom {
 
+using namespace mozilla::services;
+
 namespace {
 
+#define XPCOM_SHUTDOWN_OBSERVER_TOPIC "xpcom-shutdown"
+
 typedef nsDataHashtable<nsCStringHashKey, LSDatabase*> LSDatabaseHashtable;
 
 StaticAutoPtr<LSDatabaseHashtable> gLSDatabases;
 
 }  // namespace
 
+StaticRefPtr<LSDatabase::Observer> LSDatabase::sObserver;
+
+class LSDatabase::Observer final : public nsIObserver {
+ public:
+  Observer() { MOZ_ASSERT(NS_IsMainThread()); }
+
+ private:
+  ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+};
+
 LSDatabase::LSDatabase(const nsACString& aOrigin)
     : mActor(nullptr),
       mSnapshot(nullptr),
       mOrigin(aOrigin),
       mAllowedToClose(false),
       mRequestedAllowToClose(false) {
   AssertIsOnOwningThread();
 
   if (!gLSDatabases) {
     gLSDatabases = new LSDatabaseHashtable();
+
+    MOZ_ASSERT(!sObserver);
+
+    sObserver = new Observer();
+
+    nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+    MOZ_ASSERT(obsSvc);
+
+    MOZ_ALWAYS_SUCCEEDS(
+        obsSvc->AddObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC, false));
   }
 
   MOZ_ASSERT(!gLSDatabases->Get(mOrigin));
   gLSDatabases->Put(mOrigin, this);
 }
 
 LSDatabase::~LSDatabase() {
   AssertIsOnOwningThread();
@@ -57,17 +84,20 @@ void LSDatabase::SetActor(LSDatabaseChil
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(!mActor);
 
   mActor = aActor;
 }
 
 void LSDatabase::RequestAllowToClose() {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(!mRequestedAllowToClose);
+
+  if (mRequestedAllowToClose) {
+    return;
+  }
 
   mRequestedAllowToClose = true;
 
   if (mSnapshot) {
     mSnapshot->MarkDirty();
   } else {
     AllowToClose();
   }
@@ -310,13 +340,48 @@ void LSDatabase::AllowToClose() {
   }
 
   MOZ_ASSERT(gLSDatabases);
   MOZ_ASSERT(gLSDatabases->Get(mOrigin));
   gLSDatabases->Remove(mOrigin);
 
   if (!gLSDatabases->Count()) {
     gLSDatabases = nullptr;
+
+    MOZ_ASSERT(sObserver);
+
+    nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
+    MOZ_ASSERT(obsSvc);
+
+    MOZ_ALWAYS_SUCCEEDS(
+        obsSvc->RemoveObserver(sObserver, XPCOM_SHUTDOWN_OBSERVER_TOPIC));
+
+    sObserver = nullptr;
   }
 }
 
+NS_IMPL_ISUPPORTS(LSDatabase::Observer, nsIObserver)
+
+NS_IMETHODIMP
+LSDatabase::Observer::Observe(nsISupports* aSubject, const char* aTopic,
+                              const char16_t* aData) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aTopic, XPCOM_SHUTDOWN_OBSERVER_TOPIC));
+  MOZ_ASSERT(gLSDatabases);
+
+  nsTArray<RefPtr<LSDatabase>> databases;
+
+  for (auto iter = gLSDatabases->ConstIter(); !iter.Done(); iter.Next()) {
+    LSDatabase* database = iter.Data();
+    MOZ_ASSERT(database);
+
+    databases.AppendElement(database);
+  }
+
+  for (RefPtr<LSDatabase>& database : databases) {
+    database->RequestAllowToClose();
+  }
+
+  return NS_OK;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/localstorage/LSDatabase.h
+++ b/dom/localstorage/LSDatabase.h
@@ -9,25 +9,29 @@
 
 namespace mozilla {
 namespace dom {
 
 class LSDatabaseChild;
 class LSSnapshot;
 
 class LSDatabase final {
+  class Observer;
+
   LSDatabaseChild* mActor;
 
   LSSnapshot* mSnapshot;
 
   const nsCString mOrigin;
 
   bool mAllowedToClose;
   bool mRequestedAllowToClose;
 
+  static StaticRefPtr<Observer> sObserver;
+
  public:
   explicit LSDatabase(const nsACString& aOrigin);
 
   static LSDatabase* Get(const nsACString& aOrigin);
 
   NS_INLINE_DECL_REFCOUNTING(LSDatabase)
 
   void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(LSDatabase); }
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -215,104 +215,137 @@ nsresult LSObject::CreateForWindow(nsPID
 
   if (nsContentUtils::IsSystemPrincipal(principal)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // localStorage is not available on some pages on purpose, for example
   // about:home. Match the old implementation by using GenerateOriginKey
   // for the check.
-  nsCString dummyOriginAttrSuffix;
-  nsCString dummyOriginKey;
-  nsresult rv =
-      GenerateOriginKey(principal, dummyOriginAttrSuffix, dummyOriginKey);
+  nsCString originAttrSuffix;
+  nsCString originKey;
+  nsresult rv = GenerateOriginKey(principal, originAttrSuffix, originKey);
   if (NS_FAILED(rv)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
   rv = PrincipalToPrincipalInfo(principal, principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo);
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(*principalInfo))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCString suffix;
   nsCString origin;
-  rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin);
+  rv = QuotaManager::GetInfoFromPrincipal(principal, &suffix, nullptr, &origin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  MOZ_ASSERT(originAttrSuffix == suffix);
+
   uint32_t privateBrowsingId;
   rv = principal->GetPrivateBrowsingId(&privateBrowsingId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  Maybe<ClientInfo> clientInfo = aWindow->GetClientInfo();
+  if (clientInfo.isNothing()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  Maybe<nsID> clientId = Some(clientInfo.ref().Id());
+
   nsString documentURI;
   if (nsCOMPtr<Document> doc = aWindow->GetExtantDoc()) {
     rv = doc->GetDocumentURI(documentURI);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   RefPtr<LSObject> object = new LSObject(aWindow, principal);
   object->mPrincipalInfo = std::move(principalInfo);
   object->mPrivateBrowsingId = privateBrowsingId;
+  object->mClientId = clientId;
   object->mOrigin = origin;
+  object->mOriginKey = originKey;
   object->mDocumentURI = documentURI;
 
   object.forget(aStorage);
   return NS_OK;
 }
 
 // static
 nsresult LSObject::CreateForPrincipal(nsPIDOMWindowInner* aWindow,
                                       nsIPrincipal* aPrincipal,
                                       const nsAString& aDocumentURI,
                                       bool aPrivate, LSObject** aObject) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aObject);
 
-  nsCString dummyOriginAttrSuffix;
-  nsCString dummyOriginKey;
-  nsresult rv =
-      GenerateOriginKey(aPrincipal, dummyOriginAttrSuffix, dummyOriginKey);
+  nsCString originAttrSuffix;
+  nsCString originKey;
+  nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
   if (NS_FAILED(rv)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
   rv = PrincipalToPrincipalInfo(aPrincipal, principalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(principalInfo->type() == PrincipalInfo::TContentPrincipalInfo ||
              principalInfo->type() == PrincipalInfo::TSystemPrincipalInfo);
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(*principalInfo))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCString suffix;
   nsCString origin;
 
   if (principalInfo->type() == PrincipalInfo::TSystemPrincipalInfo) {
-    QuotaManager::GetInfoForChrome(nullptr, nullptr, &origin);
+    QuotaManager::GetInfoForChrome(&suffix, nullptr, &origin);
   } else {
-    rv = QuotaManager::GetInfoFromPrincipal(aPrincipal, nullptr, nullptr,
+    rv = QuotaManager::GetInfoFromPrincipal(aPrincipal, &suffix, nullptr,
                                             &origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
+  MOZ_ASSERT(originAttrSuffix == suffix);
+
+  Maybe<nsID> clientId;
+  if (aWindow) {
+    Maybe<ClientInfo> clientInfo = aWindow->GetClientInfo();
+    if (clientInfo.isNothing()) {
+      return NS_ERROR_FAILURE;
+    }
+
+    clientId = Some(clientInfo.ref().Id());
+  }
+
   RefPtr<LSObject> object = new LSObject(aWindow, aPrincipal);
   object->mPrincipalInfo = std::move(principalInfo);
   object->mPrivateBrowsingId = aPrivate ? 1 : 0;
+  object->mClientId = clientId;
   object->mOrigin = origin;
+  object->mOriginKey = originKey;
   object->mDocumentURI = aDocumentURI;
 
   object.forget(aObject);
   return NS_OK;
 }
 
 // static
 already_AddRefed<nsISerialEventTarget> LSObject::GetSyncLoopEventTarget() {
@@ -727,37 +760,38 @@ nsresult LSObject::EnsureDatabase() {
   // too late to initialize PBackground child on the owning thread, because
   // it can fail and parent would keep an extra strong ref to the datastore.
   PBackgroundChild* backgroundActor =
       BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!backgroundActor)) {
     return NS_ERROR_FAILURE;
   }
 
+  LSRequestCommonParams commonParams;
+  commonParams.principalInfo() = *mPrincipalInfo;
+  commonParams.originKey() = mOriginKey;
+
   LSRequestPrepareDatastoreParams params;
-  params.principalInfo() = *mPrincipalInfo;
-  params.createIfNotExists() = true;
+  params.commonParams() = commonParams;
+  params.clientId() = mClientId;
 
   LSRequestResponse response;
 
   nsresult rv = DoRequestSynchronously(params, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   MOZ_ASSERT(response.type() ==
              LSRequestResponse::TLSRequestPrepareDatastoreResponse);
 
   const LSRequestPrepareDatastoreResponse& prepareDatastoreResponse =
       response.get_LSRequestPrepareDatastoreResponse();
 
-  const NullableDatastoreId& datastoreId =
-      prepareDatastoreResponse.datastoreId();
-
-  MOZ_ASSERT(datastoreId.type() == NullableDatastoreId::Tuint64_t);
+  uint64_t datastoreId = prepareDatastoreResponse.datastoreId();
 
   // The datastore is now ready on the parent side (prepared by the asynchronous
   // request on the DOM File thread).
   // Let's create a direct connection to the datastore (through a database
   // actor) from the owning thread.
   // Note that we now can't error out, otherwise parent will keep an extra
   // strong reference to the datastore.
 
@@ -796,16 +830,17 @@ nsresult LSObject::EnsureObserver() {
   mObserver = LSObserver::Get(mOrigin);
 
   if (mObserver) {
     return NS_OK;
   }
 
   LSRequestPrepareObserverParams params;
   params.principalInfo() = *mPrincipalInfo;
+  params.clientId() = mClientId;
 
   LSRequestResponse response;
 
   nsresult rv = DoRequestSynchronously(params, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -1007,30 +1042,32 @@ nsresult RequestHelper::StartAndReturnRe
         gSyncLoopEventTarget = nullptr;
       });
 
       rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
-      MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() {
-        if (!mWaiting) {
-          return true;
-        }
+      MOZ_ALWAYS_TRUE(SpinEventLoopUntil(
+          [&]() {
+            if (!mWaiting) {
+              return true;
+            }
 
-        {
-          StaticMutexAutoLock lock(gRequestHelperMutex);
-          if (NS_WARN_IF(gPendingSyncMessage)) {
-            return true;
-          }
-        }
+            {
+              StaticMutexAutoLock lock(gRequestHelperMutex);
+              if (NS_WARN_IF(gPendingSyncMessage)) {
+                return true;
+              }
+            }
 
-        return false;
-      }, thread));
+            return false;
+          },
+          thread));
     }
 
     // If mWaiting is still set to true, it means that the event loop spinning
     // was aborted and we need to cancel the request in the parent since we
     // don't care about the result anymore.
     // We can check mWaiting here because it's only ever touched on the main
     // thread.
     if (NS_WARN_IF(mWaiting)) {
--- a/dom/localstorage/LSObject.h
+++ b/dom/localstorage/LSObject.h
@@ -61,17 +61,19 @@ class LSObject final : public Storage {
   friend nsGlobalWindowInner;
 
   nsAutoPtr<PrincipalInfo> mPrincipalInfo;
 
   RefPtr<LSDatabase> mDatabase;
   RefPtr<LSObserver> mObserver;
 
   uint32_t mPrivateBrowsingId;
+  Maybe<nsID> mClientId;
   nsCString mOrigin;
+  nsCString mOriginKey;
   nsString mDocumentURI;
 
   bool mInExplicitSnapshot;
 
  public:
   /**
    * The normal creation path invoked by nsGlobalWindowInner.
    */
--- a/dom/localstorage/LocalStorageCommon.cpp
+++ b/dom/localstorage/LocalStorageCommon.cpp
@@ -1,38 +1,137 @@
 /* -*- 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 "LocalStorageCommon.h"
 
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/net/MozURL.h"
+
 namespace mozilla {
 namespace dom {
 
+using namespace mozilla::net;
+
 namespace {
 
+StaticMutex gNextGenLocalStorageMutex;
 Atomic<int32_t> gNextGenLocalStorageEnabled(-1);
 
 }  // namespace
 
 const char16_t* kLocalStorageType = u"localStorage";
 
 bool NextGenLocalStorageEnabled() {
+  if (XRE_IsParentProcess()) {
+    StaticMutexAutoLock lock(gNextGenLocalStorageMutex);
+
+    if (gNextGenLocalStorageEnabled == -1) {
+      bool enabled = GetCurrentNextGenPrefValue();
+      gNextGenLocalStorageEnabled = enabled ? 1 : 0;
+    }
+
+    return !!gNextGenLocalStorageEnabled;
+  }
+
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gNextGenLocalStorageEnabled == -1) {
     bool enabled = Preferences::GetBool("dom.storage.next_gen", false);
     gNextGenLocalStorageEnabled = enabled ? 1 : 0;
   }
 
   return !!gNextGenLocalStorageEnabled;
 }
 
 bool CachedNextGenLocalStorageEnabled() {
   MOZ_ASSERT(gNextGenLocalStorageEnabled != -1);
 
   return !!gNextGenLocalStorageEnabled;
 }
 
+nsresult GenerateOriginKey2(const PrincipalInfo& aPrincipalInfo,
+                            nsACString& aOriginAttrSuffix,
+                            nsACString& aOriginKey) {
+  OriginAttributes attrs;
+  nsCString spec;
+
+  switch (aPrincipalInfo.type()) {
+    case PrincipalInfo::TNullPrincipalInfo: {
+      const NullPrincipalInfo& info = aPrincipalInfo.get_NullPrincipalInfo();
+
+      attrs = info.attrs();
+      spec = info.spec();
+
+      break;
+    }
+
+    case PrincipalInfo::TContentPrincipalInfo: {
+      const ContentPrincipalInfo& info =
+          aPrincipalInfo.get_ContentPrincipalInfo();
+
+      attrs = info.attrs();
+      spec = info.spec();
+
+      break;
+    }
+
+    default: {
+      spec.SetIsVoid(true);
+
+      break;
+    }
+  }
+
+  if (spec.IsVoid()) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  attrs.CreateSuffix(aOriginAttrSuffix);
+
+  RefPtr<MozURL> specURL;
+  nsresult rv = MozURL::Init(getter_AddRefs(specURL), spec);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsCString host(specURL->Host());
+  uint32_t length = host.Length();
+  if (length > 0 && host.CharAt(0) == '[' && host.CharAt(length - 1) == ']') {
+    host = Substring(host, 1, length - 2);
+  }
+
+  nsCString domainOrigin(host);
+
+  if (domainOrigin.IsEmpty()) {
+    // For the file:/// protocol use the exact directory as domain.
+    if (specURL->Scheme().EqualsLiteral("file")) {
+      domainOrigin.Assign(specURL->Directory());
+    }
+  }
+
+  // Append reversed domain
+  nsAutoCString reverseDomain;
+  rv = CreateReversedDomain(domainOrigin, reverseDomain);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  aOriginKey.Append(reverseDomain);
+
+  // Append scheme
+  aOriginKey.Append(':');
+  aOriginKey.Append(specURL->Scheme());
+
+  // Append port if any
+  int32_t port = specURL->RealPort();
+  if (port != -1) {
+    aOriginKey.Append(nsPrintfCString(":%d", port));
+  }
+
+  return NS_OK;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/localstorage/LocalStorageCommon.h
+++ b/dom/localstorage/LocalStorageCommon.h
@@ -176,17 +176,27 @@
  * ~~~~~~~~~~~~~~~~~~~~~
  *
  * The local storage manager exposes some of the features that need to be
  * available only in the chrome code or tests. The manager is represented by
  * the "LocalStorageManager2" class that implements the "nsIDOMStorageManager"
  * interface.
  */
 
+#include "mozilla/Attributes.h"
+#include "nsString.h"
+
 namespace mozilla {
+
+namespace ipc {
+
+class PrincipalInfo;
+
+}  // namespace ipc
+
 namespace dom {
 
 extern const char16_t* kLocalStorageType;
 
 /**
  * Convenience data-structure to make it easier to track whether a value has
  * changed and what its previous value was for notification purposes.  Instances
  * are created on the stack by LSObject and passed to LSDatabase which in turn
@@ -205,22 +215,30 @@ class MOZ_STACK_CLASS LSNotifyInfo {
   bool& changed() { return mChanged; }
 
   const nsString& oldValue() const { return mOldValue; }
 
   nsString& oldValue() { return mOldValue; }
 };
 
 /**
- * Main-thread-only check of LSNG being enabled, the value is latched once
- * initialized so changing the preference during runtime has no effect.
+ * A check of LSNG being enabled, the value is latched once initialized so
+ * changing the preference during runtime has no effect.
+ * May be called on any thread in the parent process, but you should call
+ * CachedNextGenLocalStorageEnabled if you know that NextGenLocalStorageEnabled
+ * was already called because it is faster.
+ * May be called on the main thread only in a content process.
  */
 bool NextGenLocalStorageEnabled();
 
 /**
  * Cached any-thread version of NextGenLocalStorageEnabled().
  */
 bool CachedNextGenLocalStorageEnabled();
 
+nsresult GenerateOriginKey2(const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                            nsACString& aOriginAttrSuffix,
+                            nsACString& aOriginKey);
+
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_localstorage_LocalStorageCommon_h
--- a/dom/localstorage/LocalStorageManager2.cpp
+++ b/dom/localstorage/LocalStorageManager2.cpp
@@ -22,17 +22,17 @@ class RequestResolver final : public LSR
 
   NS_INLINE_DECL_REFCOUNTING(mozilla::dom::RequestResolver, override);
 
  private:
   ~RequestResolver() = default;
 
   void HandleResponse(nsresult aResponse);
 
-  void HandleResponse(const NullableDatastoreId& aDatastoreId);
+  void HandleResponse();
 
   // LSRequestChildCallback
   void OnResponse(const LSRequestResponse& aResponse) override;
 };
 
 class SimpleRequestResolver final : public LSSimpleRequestChildCallback {
   RefPtr<Promise> mPromise;
 
@@ -77,16 +77,20 @@ nsresult CheckedPrincipalToPrincipalInfo
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
 
   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
+    return NS_ERROR_FAILURE;
+  }
+
   if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
       aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
@@ -183,34 +187,43 @@ LocalStorageManager2::GetNextGenLocalSto
 
 NS_IMETHODIMP
 LocalStorageManager2::Preload(nsIPrincipal* aPrincipal, JSContext* aContext,
                               nsISupports** _retval) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(_retval);
 
-  nsresult rv;
+  nsCString originAttrSuffix;
+  nsCString originKey;
+  nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+  rv = CheckedPrincipalToPrincipalInfo(aPrincipal, *principalInfo);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   RefPtr<Promise> promise;
 
   if (aContext) {
     rv = CreatePromise(aContext, getter_AddRefs(promise));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
-  LSRequestPrepareDatastoreParams params;
-  params.createIfNotExists() = false;
+  LSRequestCommonParams commonParams;
+  commonParams.principalInfo() = *principalInfo;
+  commonParams.originKey() = originKey;
 
-  rv = CheckedPrincipalToPrincipalInfo(aPrincipal, params.principalInfo());
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+  LSRequestPreloadDatastoreParams params(commonParams);
 
   rv = StartRequest(promise, params);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   promise.forget(_retval);
   return NS_OK;
@@ -294,48 +307,36 @@ void RequestResolver::HandleResponse(nsr
 
   if (!mPromise) {
     return;
   }
 
   mPromise->MaybeReject(aResponse);
 }
 
-void RequestResolver::HandleResponse(const NullableDatastoreId& aDatastoreId) {
+void RequestResolver::HandleResponse() {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mPromise) {
     return;
   }
 
-  switch (aDatastoreId.type()) {
-    case NullableDatastoreId::Tnull_t:
-      mPromise->MaybeResolve(JS::NullHandleValue);
-      break;
-
-    case NullableDatastoreId::Tuint64_t:
-      mPromise->MaybeResolve(aDatastoreId.get_uint64_t());
-      break;
-
-    default:
-      MOZ_CRASH("Unknown datastore id type!");
-  }
+  mPromise->MaybeResolveWithUndefined();
 }
 
 void RequestResolver::OnResponse(const LSRequestResponse& aResponse) {
   MOZ_ASSERT(NS_IsMainThread());
 
   switch (aResponse.type()) {
     case LSRequestResponse::Tnsresult:
       HandleResponse(aResponse.get_nsresult());
       break;
 
-    case LSRequestResponse::TLSRequestPrepareDatastoreResponse:
-      HandleResponse(
-          aResponse.get_LSRequestPrepareDatastoreResponse().datastoreId());
+    case LSRequestResponse::TLSRequestPreloadDatastoreResponse:
+      HandleResponse();
       break;
     default:
       MOZ_CRASH("Unknown response type!");
   }
 }
 
 void SimpleRequestResolver::HandleResponse(nsresult aResponse) {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/localstorage/PBackgroundLSRequest.ipdl
+++ b/dom/localstorage/PBackgroundLSRequest.ipdl
@@ -5,39 +5,38 @@
 include protocol PBackground;
 
 using struct mozilla::null_t
   from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace dom {
 
-union NullableDatastoreId
+struct LSRequestPreloadDatastoreResponse
 {
-  null_t;
-  uint64_t;
 };
 
 struct LSRequestPrepareDatastoreResponse
 {
-  NullableDatastoreId datastoreId;
+  uint64_t datastoreId;
 };
 
 struct LSRequestPrepareObserverResponse
 {
   uint64_t observerId;
 };
 
 /**
  * Discriminated union which can contain an error code (`nsresult`) or
  * particular request response.
  */
 union LSRequestResponse
 {
   nsresult;
+  LSRequestPreloadDatastoreResponse;
   LSRequestPrepareDatastoreResponse;
   LSRequestPrepareObserverResponse;
 };
 
 /**
  * An asynchronous protocol for issuing requests that are used in a synchronous
  * fashion by LocalStorage via LSObject's RequestHelper mechanism.  This differs
  * from LSSimpleRequest which is implemented and used asynchronously.
--- a/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
+++ b/dom/localstorage/PBackgroundLSSharedTypes.ipdlh
@@ -1,30 +1,44 @@
 /* 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 PBackgroundSharedTypes;
+include ProtocolTypes;
 
 namespace mozilla {
 namespace dom {
 
+struct LSRequestCommonParams
+{
+  PrincipalInfo principalInfo;
+  nsCString originKey;
+};
+
+struct LSRequestPreloadDatastoreParams
+{
+  LSRequestCommonParams commonParams;
+};
+
 struct LSRequestPrepareDatastoreParams
 {
-  PrincipalInfo principalInfo;
-  bool createIfNotExists;
+  LSRequestCommonParams commonParams;
+  nsID? clientId;
 };
 
 struct LSRequestPrepareObserverParams
 {
   PrincipalInfo principalInfo;
+  nsID? clientId;
 };
 
 union LSRequestParams
 {
+  LSRequestPreloadDatastoreParams;
   LSRequestPrepareDatastoreParams;
   LSRequestPrepareObserverParams;
 };
 
 struct LSSimpleRequestPreloadedParams
 {
   PrincipalInfo principalInfo;
 };
--- a/dom/localstorage/moz.build
+++ b/dom/localstorage/moz.build
@@ -7,16 +7,18 @@
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/xpcshell.ini'
 ]
 
 TEST_HARNESS_FILES.xpcshell.dom.localstorage.test.unit += [
     'test/unit/databaseShadowing-shared.js',
 ]
 
+TEST_DIRS += ['test/gtest']
+
 XPIDL_SOURCES += [
     'nsILocalStorageManager.idl',
 ]
 
 XPIDL_MODULE = 'dom_localstorage'
 
 EXPORTS.mozilla.dom.localstorage += [
     'ActorsParent.h',
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/gtest/TestLocalStorage.cpp
@@ -0,0 +1,113 @@
+/* -*- 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 "gtest/gtest.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/dom/LocalStorageCommon.h"
+#include "mozilla/dom/StorageUtils.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::StorageUtils;
+using namespace mozilla::ipc;
+
+namespace {
+
+struct OriginKeyTest {
+  const char* mSpec;
+  const char* mOriginKey;
+};
+
+already_AddRefed<nsIPrincipal> GetCodebasePrincipal(const char* aSpec) {
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aSpec));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return nullptr;
+  }
+
+  OriginAttributes attrs;
+
+  nsCOMPtr<nsIPrincipal> principal =
+      BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+
+  return principal.forget();
+}
+
+void CheckGeneratedOriginKey(nsIPrincipal* aPrincipal, const char* aOriginKey) {
+  nsCString originAttrSuffix;
+  nsCString originKey;
+  nsresult rv = GenerateOriginKey(aPrincipal, originAttrSuffix, originKey);
+  if (aOriginKey) {
+    ASSERT_EQ(rv, NS_OK) << "GenerateOriginKey should not fail";
+    EXPECT_TRUE(originKey == nsDependentCString(aOriginKey));
+  } else {
+    ASSERT_NE(rv, NS_OK) << "GenerateOriginKey should fail";
+  }
+
+  PrincipalInfo principalInfo;
+  rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
+  ASSERT_EQ(rv, NS_OK) << "PrincipalToPrincipalInfo should not fail";
+
+  originKey.Truncate();
+  rv = GenerateOriginKey2(principalInfo, originAttrSuffix, originKey);
+  if (aOriginKey) {
+    ASSERT_EQ(rv, NS_OK) << "GenerateOriginKey2 should not fail";
+    EXPECT_TRUE(originKey == nsDependentCString(aOriginKey));
+  } else {
+    ASSERT_NE(rv, NS_OK) << "GenerateOriginKey2 should fail";
+  }
+}
+
+}  // namespace
+
+TEST(LocalStorage, OriginKey) {
+  // Check the system principal.
+  nsCOMPtr<nsIScriptSecurityManager> secMan =
+      nsContentUtils::GetSecurityManager();
+  ASSERT_TRUE(secMan) << "GetSecurityManager() should not fail";
+
+  nsCOMPtr<nsIPrincipal> principal;
+  secMan->GetSystemPrincipal(getter_AddRefs(principal));
+  ASSERT_TRUE(principal) << "GetSystemPrincipal() should not fail";
+
+  CheckGeneratedOriginKey(principal, nullptr);
+
+  // Check the null principal.
+  principal = NullPrincipal::CreateWithoutOriginAttributes();
+  ASSERT_TRUE(principal) << "CreateWithoutOriginAttributes() should not fail";
+
+  CheckGeneratedOriginKey(principal, nullptr);
+
+  // Check content principals.
+  static const OriginKeyTest tests[] = {
+      {"http://www.mozilla.org", "gro.allizom.www.:http:80"},
+      {"https://www.mozilla.org", "gro.allizom.www.:https:443"},
+      {"http://www.mozilla.org:32400", "gro.allizom.www.:http:32400"},
+      {"file:///Users/Joe/Sites/", "/setiS/eoJ/sresU/.:file"},
+      {"file:///Users/Joe/Sites/#foo", "/setiS/eoJ/sresU/.:file"},
+      {"file:///Users/Joe/Sites/?foo", "/setiS/eoJ/sresU/.:file"},
+      {"file:///Users/Joe/Sites", "/eoJ/sresU/.:file"},
+      {"file:///Users/Joe/Sites#foo", "/eoJ/sresU/.:file"},
+      {"file:///Users/Joe/Sites?foo", "/eoJ/sresU/.:file"},
+      {"moz-extension://53711a8f-65ed-e742-9671-1f02e267c0bc/"
+       "_generated_background_page.html",
+       "cb0c762e20f1-1769-247e-de56-f8a11735.:moz-extension"},
+      {"http://[::1]:8/test.html", "1::.:http:8"},
+  };
+
+  for (const auto& test : tests) {
+    principal = GetCodebasePrincipal(test.mSpec);
+    ASSERT_TRUE(principal) << "GetCodebasePrincipal() should not fail";
+
+    CheckGeneratedOriginKey(principal, test.mOriginKey);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/gtest/moz.build
@@ -0,0 +1,17 @@
+# -*- 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/.
+
+UNIFIED_SOURCES = [
+    'TestLocalStorage.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul-gtest'
+
+LOCAL_INCLUDES += [
+    '/dom/localstorage',
+]
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ActorsParent.h"
 
 #include "mozIStorageConnection.h"
 #include "mozIStorageService.h"
+#include "mozIThirdPartyUtil.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIFile.h"
 #include "nsIFileStreams.h"
 #include "nsIObserverService.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIRunnable.h"
@@ -22,32 +23,36 @@
 #include "nsISupportsPrimitives.h"
 #include "nsITimer.h"
 #include "nsIURI.h"
 #include "nsPIDOMWindow.h"
 
 #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"
 #include "mozilla/dom/StorageDBUpdater.h"
+#include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/net/MozURL.h"
 #include "mozilla/IntegerRange.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TypeTraits.h"
@@ -113,16 +118,17 @@
 #define MB *1024ULL KB
 #define GB *1024ULL MB
 
 namespace mozilla {
 namespace dom {
 namespace quota {
 
 using namespace mozilla::ipc;
+using mozilla::net::MozURL;
 
 // We want profiles to be platform-independent so we always need to replace
 // the same characters on every platform. Windows has the most extensive set
 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
 // FILE_PATH_SEPARATOR.
 const char QuotaManager::kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
 
 namespace {
@@ -210,16 +216,18 @@ enum AppId {
 #define METADATA_TMP_FILE_NAME ".metadata-tmp"
 #define METADATA_V2_FILE_NAME ".metadata-v2"
 #define METADATA_V2_TMP_FILE_NAME ".metadata-v2-tmp"
 
 #define WEB_APPS_STORE_FILE_NAME "webappsstore.sqlite"
 #define LS_ARCHIVE_FILE_NAME "ls-archive.sqlite"
 #define LS_ARCHIVE_TMP_FILE_NAME "ls-archive-tmp.sqlite"
 
+const char kProfileDoChangeTopic[] = "profile-do-change";
+
 /******************************************************************************
  * SQLite functions
  ******************************************************************************/
 
 int32_t MakeStorageVersion(uint32_t aMajorStorageVersion,
                            uint32_t aMinorStorageVersion) {
   return int32_t((aMajorStorageVersion << 16) + aMinorStorageVersion);
 }
@@ -408,96 +416,40 @@ class DirectoryLockImpl final : public D
   }
 
   NS_INLINE_DECL_REFCOUNTING(DirectoryLockImpl, override)
 
  private:
   ~DirectoryLockImpl();
 };
 
-class QuotaManager::CreateRunnable final : public BackgroundThreadObject,
-                                           public Runnable {
-  nsCOMPtr<nsIEventTarget> mMainEventTarget;
-  nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
-  nsString mBaseDirPath;
-  RefPtr<QuotaManager> mManager;
-  nsresult mResultCode;
-
-  enum class State {
-    Initial,
-    CreatingManager,
-    RegisteringObserver,
-    CallingCallbacks,
-    Completed
-  };
-
-  State mState;
+class QuotaManager::Observer final : public nsIObserver {
+  static Observer* sInstance;
+
+  bool mPendingProfileChange;
+  bool mShutdownComplete;
 
  public:
-  explicit CreateRunnable(nsIEventTarget* aMainEventTarget)
-      : Runnable("dom::quota::QuotaManager::CreateRunnable"),
-        mMainEventTarget(aMainEventTarget),
-        mResultCode(NS_OK),
-        mState(State::Initial) {
-    AssertIsOnBackgroundThread();
-  }
-
-  void AddCallback(nsIRunnable* aCallback) {
-    AssertIsOnOwningThread();
-    MOZ_ASSERT(aCallback);
-
-    mCallbacks.AppendElement(aCallback);
-  }
+  static nsresult Initialize();
+
+  static void ShutdownCompleted();
 
  private:
-  ~CreateRunnable() {}
+  Observer() : mPendingProfileChange(false), mShutdownComplete(false) {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  ~Observer() { MOZ_ASSERT(NS_IsMainThread()); }
 
   nsresult Init();
 
-  nsresult CreateManager();
-
-  nsresult RegisterObserver();
-
-  void CallCallbacks();
-
-  State GetNextState(nsCOMPtr<nsIEventTarget>& aThread);
-
-  NS_DECL_NSIRUNNABLE
-};
-
-class QuotaManager::ShutdownRunnable final : public Runnable {
-  // Only touched on the main thread.
-  bool& mDone;
-
- public:
-  explicit ShutdownRunnable(bool& aDone)
-      : Runnable("dom::quota::QuotaManager::ShutdownRunnable"), mDone(aDone) {
-    MOZ_ASSERT(NS_IsMainThread());
-  }
-
- private:
-  ~ShutdownRunnable() {}
-
-  NS_DECL_NSIRUNNABLE
-};
-
-class QuotaManager::ShutdownObserver final : public nsIObserver {
-  nsCOMPtr<nsIEventTarget> mBackgroundThread;
-
- public:
-  explicit ShutdownObserver(nsIEventTarget* aBackgroundThread)
-      : mBackgroundThread(aBackgroundThread) {
-    MOZ_ASSERT(NS_IsMainThread());
-  }
+  nsresult Shutdown();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
-
- private:
-  ~ShutdownObserver() { MOZ_ASSERT(NS_IsMainThread()); }
 };
 
 namespace {
 
 /*******************************************************************************
  * Local class declarations
  ******************************************************************************/
 
@@ -695,22 +647,16 @@ class CollectOriginsHelper final : publi
 class OriginOperationBase : public BackgroundThreadObject, public Runnable {
  protected:
   nsresult mResultCode;
 
   enum State {
     // Not yet run.
     State_Initial,
 
-    // Running initialization on the main thread.
-    State_Initializing,
-
-    // Running initialization on the owning thread.
-    State_FinishingInit,
-
     // Running quota manager initialization on the owning thread.
     State_CreatingQuotaManager,
 
     // Running on the owning thread in the listener for OpenDirectory.
     State_DirectoryOpenPending,
 
     // Running on the IO thread.
     State_DirectoryWorkOpen,
@@ -767,22 +713,16 @@ class OriginOperationBase : public Backg
   void SetState(State aState) {
     MOZ_ASSERT(mState == State_Initial);
     mState = aState;
   }
 
   void AdvanceState() {
     switch (mState) {
       case State_Initial:
-        mState = State_Initializing;
-        return;
-      case State_Initializing:
-        mState = State_FinishingInit;
-        return;
-      case State_FinishingInit:
         mState = State_CreatingQuotaManager;
         return;
       case State_CreatingQuotaManager:
         mState = State_DirectoryOpenPending;
         return;
       case State_DirectoryOpenPending:
         mState = State_DirectoryWorkOpen;
         return;
@@ -795,33 +735,29 @@ class OriginOperationBase : public Backg
       default:
         MOZ_CRASH("Bad state!");
     }
   }
 
   NS_IMETHOD
   Run() override;
 
-  virtual nsresult DoInitOnMainThread() { return NS_OK; }
-
   virtual void Open() = 0;
 
   nsresult DirectoryOpen();
 
   virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) = 0;
 
   void Finish(nsresult aResult);
 
   virtual void UnblockOpen() = 0;
 
  private:
   nsresult Init();
 
-  nsresult InitOnMainThread();
-
   nsresult FinishInit();
 
   nsresult QuotaManagerOpen();
 
   nsresult DirectoryWork();
 };
 
 class FinalizeOriginEvictionOp : public OriginOperationBase {
@@ -932,16 +868,20 @@ class Quota final : public PQuotaParent 
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::quota::Quota)
 
  private:
   ~Quota();
 
   void StartIdleMaintenance();
 
+  bool VerifyRequestParams(const UsageRequestParams& aParams) const;
+
+  bool VerifyRequestParams(const RequestParams& aParams) const;
+
   // IPDL methods.
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual PQuotaUsageRequestParent* AllocPQuotaUsageRequestParent(
       const UsageRequestParams& aParams) override;
 
   virtual mozilla::ipc::IPCResult RecvPQuotaUsageRequestConstructor(
       PQuotaUsageRequestParent* aActor,
@@ -956,16 +896,19 @@ class Quota final : public PQuotaParent 
   virtual mozilla::ipc::IPCResult RecvPQuotaRequestConstructor(
       PQuotaRequestParent* aActor, const RequestParams& aParams) override;
 
   virtual bool DeallocPQuotaRequestParent(PQuotaRequestParent* aActor) override;
 
   virtual mozilla::ipc::IPCResult RecvStartIdleMaintenance() override;
 
   virtual mozilla::ipc::IPCResult RecvStopIdleMaintenance() override;
+
+  virtual mozilla::ipc::IPCResult RecvAbortOperationsForProcess(
+      const ContentParentId& aContentParentId) override;
 };
 
 class QuotaUsageRequestBase : public NormalOriginOperationBase,
                               public PQuotaUsageRequestParent {
  public:
   // May be overridden by subclasses if they need to perform work on the
   // background thread before being run.
   virtual bool Init(Quota* aQuota);
@@ -1027,18 +970,16 @@ class GetOriginUsageOp final : public Qu
  public:
   explicit GetOriginUsageOp(const UsageRequestParams& aParams);
 
   MOZ_IS_CLASS_INIT bool Init(Quota* aQuota) override;
 
  private:
   ~GetOriginUsageOp() {}
 
-  MOZ_IS_CLASS_INIT virtual nsresult DoInitOnMainThread() override;
-
   virtual nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
 
   void GetResponse(UsageRequestResponse& aResponse) override;
 };
 
 class QuotaRequestBase : public NormalOriginOperationBase,
                          public PQuotaRequestParent {
  public:
@@ -1098,18 +1039,16 @@ class InitOriginOp final : public QuotaR
  public:
   explicit InitOriginOp(const RequestParams& aParams);
 
   bool Init(Quota* aQuota) override;
 
  private:
   ~InitOriginOp() {}
 
-  nsresult DoInitOnMainThread() override;
-
   nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
 
   void GetResponse(RequestResponse& aResponse) override;
 };
 
 class ResetOrClearOp final : public QuotaRequestBase {
   const bool mClear;
 
@@ -1151,52 +1090,45 @@ class ClearOriginOp final : public Clear
  public:
   explicit ClearOriginOp(const RequestParams& aParams);
 
   bool Init(Quota* aQuota) override;
 
  private:
   ~ClearOriginOp() {}
 
-  nsresult DoInitOnMainThread() override;
-
   void GetResponse(RequestResponse& aResponse) override;
 };
 
 class ClearDataOp final : public ClearRequestBase {
   const ClearDataParams mParams;
 
  public:
   explicit ClearDataOp(const RequestParams& aParams);
 
   bool Init(Quota* aQuota) override;
 
  private:
   ~ClearDataOp() {}
 
-  nsresult DoInitOnMainThread() override;
-
   void GetResponse(RequestResponse& aResponse) override;
 };
 
 class PersistRequestBase : public QuotaRequestBase {
   const PrincipalInfo mPrincipalInfo;
 
  protected:
   nsCString mSuffix;
   nsCString mGroup;
 
  public:
   bool Init(Quota* aQuota) override;
 
  protected:
   explicit PersistRequestBase(const PrincipalInfo& aPrincipalInfo);
-
- private:
-  nsresult DoInitOnMainThread() override;
 };
 
 class PersistedOp final : public PersistRequestBase {
   bool mPersisted;
 
  public:
   explicit PersistedOp(const RequestParams& aParams);
 
@@ -1215,31 +1147,60 @@ class PersistOp final : public PersistRe
  private:
   ~PersistOp() {}
 
   nsresult DoDirectoryWork(QuotaManager* aQuotaManager) override;
 
   void GetResponse(RequestResponse& aResponse) override;
 };
 
+/*******************************************************************************
+ * Other class declarations
+ ******************************************************************************/
+
 class StoragePressureRunnable final : public Runnable {
   const uint64_t mUsage;
 
  public:
   explicit StoragePressureRunnable(uint64_t aUsage)
       : Runnable("dom::quota::QuotaObject::StoragePressureRunnable"),
         mUsage(aUsage) {}
 
  private:
   ~StoragePressureRunnable() = default;
 
   NS_DECL_NSIRUNNABLE
 };
 
 /*******************************************************************************
+ * Helper classes
+ ******************************************************************************/
+
+class PrincipalVerifier final : public Runnable {
+  nsTArray<PrincipalInfo> mPrincipalInfos;
+
+ public:
+  static already_AddRefed<PrincipalVerifier> CreateAndDispatch(
+      nsTArray<PrincipalInfo>&& aPrincipalInfos);
+
+ private:
+  explicit PrincipalVerifier(nsTArray<PrincipalInfo>&& aPrincipalInfos)
+      : Runnable("dom::quota::PrincipalVerifier"),
+        mPrincipalInfos(std::move(aPrincipalInfos)) {
+    AssertIsOnIOThread();
+  }
+
+  virtual ~PrincipalVerifier() = default;
+
+  bool IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo);
+
+  NS_DECL_NSIRUNNABLE
+};
+
+/*******************************************************************************
  * Helper Functions
  ******************************************************************************/
 
 template <typename T, bool = mozilla::IsUnsigned<T>::value>
 struct IntChecker {
   static void Assert(T aInt) {
     static_assert(mozilla::IsIntegral<T>::value, "Not an integer!");
     MOZ_ASSERT(aInt >= 0);
@@ -1348,56 +1309,52 @@ 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;
+
+#ifdef DEBUG
+bool gQuotaManagerInitialized = false;
+#endif
+
 StaticRefPtr<QuotaManager> gInstance;
 bool gCreateFailed = false;
-StaticRefPtr<QuotaManager::CreateRunnable> gCreateRunnable;
 mozilla::Atomic<bool> gShutdown(false);
 
 // Constants for temporary storage limit computing.
 static const int32_t kDefaultFixedLimitKB = -1;
 static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
-int32_t gFixedLimitKB = kDefaultFixedLimitKB;
-uint32_t gChunkSizeKB = kDefaultChunkSizeKB;
-
-bool gTestingEnabled = false;
-
-class StorageOperationBase : public Runnable {
-  mozilla::Mutex mMutex;
-  mozilla::CondVar mCondVar;
-  nsresult mMainThreadResultCode;
-  bool mWaiting;
-
+Atomic<int32_t, Relaxed> gFixedLimitKB(kDefaultFixedLimitKB);
+Atomic<uint32_t, Relaxed> gChunkSizeKB(kDefaultChunkSizeKB);
+
+Atomic<bool> gTestingEnabled(false);
+
+class StorageOperationBase {
  protected:
   struct OriginProps;
 
   nsTArray<OriginProps> mOriginProps;
 
   nsCOMPtr<nsIFile> mDirectory;
 
   const bool mPersistent;
 
  public:
   StorageOperationBase(nsIFile* aDirectory, bool aPersistent)
-      : Runnable("dom::quota::StorageOperationBase"),
-        mMutex("StorageOperationBase::mMutex"),
-        mCondVar(mMutex, "StorageOperationBase::mCondVar"),
-        mMainThreadResultCode(NS_OK),
-        mWaiting(true),
-        mDirectory(aDirectory),
-        mPersistent(aPersistent) {
+      : mDirectory(aDirectory), mPersistent(aPersistent) {
     AssertIsOnIOThread();
   }
 
+  NS_INLINE_DECL_REFCOUNTING(StorageOperationBase)
+
  protected:
   virtual ~StorageOperationBase() {}
 
   nsresult GetDirectoryMetadata(nsIFile* aDirectory, int64_t& aTimestamp,
                                 nsACString& aGroup, nsACString& aOrigin,
                                 Nullable<bool>& aIsApp);
 
   // Upgrade helper to load the contents of ".metadata-v2" files from previous
@@ -1410,22 +1367,16 @@ class StorageOperationBase : public Runn
                                  nsACString& aSuffix, nsACString& aGroup,
                                  nsACString& aOrigin, bool& aIsApp);
 
   nsresult RemoveObsoleteOrigin(const OriginProps& aOriginProps);
 
   nsresult ProcessOriginDirectories();
 
   virtual nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) = 0;
-
- private:
-  nsresult RunOnMainThread();
-
-  NS_IMETHOD
-  Run() override;
 };
 
 struct StorageOperationBase::OriginProps {
   enum Type { eChrome, eContent, eObsolete };
 
   nsCOMPtr<nsIFile> mDirectory;
   nsString mLeafName;
   nsCString mSpec;
@@ -2149,16 +2100,50 @@ nsresult GetTemporaryStorageLimit(nsIFil
 }
 
 }  // namespace
 
 /*******************************************************************************
  * Exported functions
  ******************************************************************************/
 
+void InitializeQuotaManager() {
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!gQuotaManagerInitialized);
+
+  if (!QuotaManager::IsRunningGTests()) {
+    // This service has to be started on the main thread currently.
+    nsCOMPtr<mozIStorageService> ss;
+    if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
+      NS_WARNING("Failed to get storage service!");
+    }
+  }
+
+  if (NS_FAILED(QuotaManager::Initialize())) {
+    NS_WARNING("Failed to initialize quota manager!");
+  }
+
+   if (NS_FAILED(Preferences::AddAtomicIntVarCache(
+           &gFixedLimitKB, PREF_FIXED_LIMIT, kDefaultFixedLimitKB)) ||
+       NS_FAILED(Preferences::AddAtomicUintVarCache(
+           &gChunkSizeKB, PREF_CHUNK_SIZE, kDefaultChunkSizeKB))) {
+    NS_WARNING("Unable to respond to temp storage pref changes!");
+  }
+
+   if (NS_FAILED(Preferences::AddAtomicBoolVarCache(
+           &gTestingEnabled, PREF_TESTING_FEATURES, false))) {
+    NS_WARNING("Unable to respond to testing pref changes!");
+  }
+
+#ifdef DEBUG
+  gQuotaManagerInitialized = true;
+#endif
+}
+
 PQuotaParent* AllocPQuotaParent() {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
     return nullptr;
   }
 
   RefPtr<Quota> actor = new Quota();
@@ -2169,16 +2154,24 @@ PQuotaParent* AllocPQuotaParent() {
 bool DeallocPQuotaParent(PQuotaParent* aActor) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   RefPtr<Quota> actor = dont_AddRef(static_cast<Quota*>(aActor));
   return true;
 }
 
+bool RecvShutdownQuotaManager() {
+  AssertIsOnBackgroundThread();
+
+  QuotaManager::ShutdownInstance();
+
+  return true;
+}
+
 /*******************************************************************************
  * Directory lock
  ******************************************************************************/
 
 DirectoryLockImpl::DirectoryLockImpl(
     QuotaManager* aQuotaManager,
     const Nullable<PersistenceType>& aPersistenceType, const nsACString& aGroup,
     const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
@@ -2269,260 +2262,163 @@ void DirectoryLockImpl::NotifyOpenListen
     mOpenListener->DirectoryLockAcquired(this);
   }
 
   mOpenListener = nullptr;
 
   mQuotaManager->RemovePendingDirectoryLock(this);
 }
 
-nsresult QuotaManager::CreateRunnable::Init() {
+QuotaManager::Observer* QuotaManager::Observer::sInstance = nullptr;
+
+// static
+nsresult QuotaManager::Observer::Initialize() {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State::Initial);
-
-  nsresult rv;
-
-  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(mBaseDirPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  Unused << NextGenLocalStorageEnabled();
+
+  RefPtr<Observer> observer = new Observer();
+
+  nsresult rv = observer->Init();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  sInstance = observer;
 
   return NS_OK;
 }
 
-nsresult QuotaManager::CreateRunnable::CreateManager() {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::CreatingManager);
-
-  mManager = new QuotaManager();
-
-  nsresult rv = mManager->Init(mBaseDirPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+// static
+void QuotaManager::Observer::ShutdownCompleted() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(sInstance);
+
+  sInstance->mShutdownComplete = true;
+}
+
+nsresult QuotaManager::Observer::Init() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = obs->AddObserver(this, kProfileDoChangeTopic, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    return rv;
+  }
+
+  rv = obs->AddObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    obs->RemoveObserver(this, kProfileDoChangeTopic);
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
     return rv;
   }
 
   return NS_OK;
 }
 
-nsresult QuotaManager::CreateRunnable::RegisterObserver() {
+nsresult QuotaManager::Observer::Shutdown() {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State::RegisteringObserver);
-
-  if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT,
-                                            kDefaultFixedLimitKB)) ||
-      NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB, PREF_CHUNK_SIZE,
-                                             kDefaultChunkSizeKB))) {
-    NS_WARNING("Unable to respond to temp storage pref changes!");
-  }
-
-  if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled,
-                                             PREF_TESTING_FEATURES, false))) {
-    NS_WARNING("Unable to respond to testing pref changes!");
-  }
-
-  nsCOMPtr<nsIObserverService> observerService =
-      mozilla::services::GetObserverService();
-  if (NS_WARN_IF(!observerService)) {
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
     return NS_ERROR_FAILURE;
   }
 
-  nsCOMPtr<nsIObserver> observer = new ShutdownObserver(mOwningThread);
-
-  nsresult rv = observerService->AddObserver(
-      observer, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID, false);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // This service has to be started on the main thread currently.
-  nsCOMPtr<mozIStorageService> ss =
-      do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  QuotaManagerService* qms = QuotaManagerService::GetOrCreate();
-  if (NS_WARN_IF(!qms)) {
-    return rv;
-  }
-
-  qms->NoteLiveManager(mManager);
-
-  for (RefPtr<Client>& client : mManager->mClients) {
-    client->DidInitialize(mManager);
-  }
+  MOZ_ALWAYS_SUCCEEDS(
+      obs->RemoveObserver(this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
+  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, kProfileDoChangeTopic));
+  MOZ_ALWAYS_SUCCEEDS(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+
+  sInstance = nullptr;
+
+  // In general, the instance will have died after the latter removal call, so
+  // it's not safe to do anything after that point.
+  // However, Shutdown is currently called from Observe which is called by the
+  // Observer Service which holds a strong reference to the observer while the
+  // Observe method is being called.
 
   return NS_OK;
 }
 
-void QuotaManager::CreateRunnable::CallCallbacks() {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::CallingCallbacks);
-
-  gCreateRunnable = nullptr;
-
-  if (NS_FAILED(mResultCode)) {
-    gCreateFailed = true;
-  } else {
-    gInstance = mManager;
-  }
-
-  mManager = nullptr;
-
-  nsTArray<nsCOMPtr<nsIRunnable>> callbacks;
-  mCallbacks.SwapElements(callbacks);
-
-  for (nsCOMPtr<nsIRunnable>& callback : callbacks) {
-    Unused << callback->Run();
-  }
-}
-
-auto QuotaManager::CreateRunnable::GetNextState(
-    nsCOMPtr<nsIEventTarget>& aThread) -> State {
-  switch (mState) {
-    case State::Initial:
-      aThread = mOwningThread;
-      return State::CreatingManager;
-    case State::CreatingManager:
-      if (mMainEventTarget) {
-        aThread = mMainEventTarget;
-      } else {
-        aThread = GetMainThreadEventTarget();
-      }
-      return State::RegisteringObserver;
-    case State::RegisteringObserver:
-      aThread = mOwningThread;
-      return State::CallingCallbacks;
-    case State::CallingCallbacks:
-      aThread = nullptr;
-      return State::Completed;
-    default:
-      MOZ_CRASH("Bad state!");
-  }
-}
+NS_IMPL_ISUPPORTS(QuotaManager::Observer, nsIObserver)
 
 NS_IMETHODIMP
-QuotaManager::CreateRunnable::Run() {
+QuotaManager::Observer::Observe(nsISupports* aSubject, const char* aTopic,
+                                const char16_t* aData) {
+  MOZ_ASSERT(NS_IsMainThread());
+
   nsresult rv;
 
-  switch (mState) {
-    case State::Initial:
-      rv = Init();
-      break;
-
-    case State::CreatingManager:
-      rv = CreateManager();
-      break;
-
-    case State::RegisteringObserver:
-      rv = RegisterObserver();
-      break;
-
-    case State::CallingCallbacks:
-      CallCallbacks();
-      rv = NS_OK;
-      break;
-
-    case State::Completed:
-    default:
-      MOZ_CRASH("Bad state!");
-  }
-
-  nsCOMPtr<nsIEventTarget> thread;
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    if (NS_SUCCEEDED(mResultCode)) {
-      mResultCode = rv;
-    }
-
-    mState = State::CallingCallbacks;
-    thread = mOwningThread;
-  } else {
-    mState = GetNextState(thread);
-  }
-
-  if (thread) {
-    MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(this, NS_DISPATCH_NORMAL));
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-QuotaManager::ShutdownRunnable::Run() {
-  if (NS_IsMainThread()) {
-    mDone = true;
+  if (!strcmp(aTopic, kProfileDoChangeTopic)) {
+    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);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
     return NS_OK;
   }
 
-  AssertIsOnBackgroundThread();
-
-  RefPtr<QuotaManager> quotaManager = gInstance.get();
-  if (quotaManager) {
-    quotaManager->Shutdown();
-
-    gInstance = nullptr;
-  }
-
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(QuotaManager::ShutdownObserver, nsIObserver)
-
-NS_IMETHODIMP
-QuotaManager::ShutdownObserver::Observe(nsISupports* aSubject,
-                                        const char* aTopic,
-                                        const char16_t* aData) {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID));
-  MOZ_ASSERT(gInstance);
-
-  nsCOMPtr<nsIObserverService> observerService =
-      mozilla::services::GetObserverService();
-  if (NS_WARN_IF(!observerService)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Unregister ourselves from the observer service first to make sure the
-  // nested event loop below will not cause re-entrancy issues.
-  Unused << observerService->RemoveObserver(
-      this, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID);
-
-  QuotaManagerService* qms = QuotaManagerService::Get();
-  MOZ_ASSERT(qms);
-
-  qms->NoteShuttingDownManager();
-
-  for (RefPtr<Client>& client : gInstance->mClients) {
-    client->WillShutdown();
-  }
-
-  bool done = false;
-
-  RefPtr<ShutdownRunnable> shutdownRunnable = new ShutdownRunnable(done);
-  MOZ_ALWAYS_SUCCEEDS(
-      mBackgroundThread->Dispatch(shutdownRunnable, NS_DISPATCH_NORMAL));
-
-  MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return done; }));
-
+  if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_QM_OBSERVER_ID)) {
+    // 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;
+
+    mShutdownComplete = false;
+
+    PBackgroundChild* backgroundActor =
+        BackgroundChild::GetOrCreateForCurrentThread();
+    if (NS_WARN_IF(!backgroundActor)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    if (NS_WARN_IF(!backgroundActor->SendShutdownQuotaManager())) {
+      return NS_ERROR_FAILURE;
+    }
+
+    MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return mShutdownComplete; }));
+
+    gBaseDirPath.Truncate();
+
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    rv = Shutdown();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    return NS_OK;
+  }
+
+  NS_WARNING("Unknown observer topic!");
   return NS_OK;
 }
 
 /*******************************************************************************
  * Quota object
  ******************************************************************************/
 
 void QuotaObject::AddRef() {
@@ -2844,55 +2740,81 @@ QuotaManager::QuotaManager()
   MOZ_ASSERT(!gInstance);
 }
 
 QuotaManager::~QuotaManager() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(!gInstance || gInstance == this);
 }
 
+// static
+nsresult QuotaManager::Initialize() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv = Observer::Initialize();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 void QuotaManager::GetOrCreate(nsIRunnable* aCallback,
                                nsIEventTarget* aMainEventTarget) {
   AssertIsOnBackgroundThread();
 
   if (IsShuttingDown()) {
     MOZ_ASSERT(false, "Calling GetOrCreate() after shutdown!");
     return;
   }
 
   if (gInstance || gCreateFailed) {
-    MOZ_ASSERT(!gCreateRunnable);
     MOZ_ASSERT_IF(gCreateFailed, !gInstance);
-
-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
   } else {
-    if (!gCreateRunnable) {
-      gCreateRunnable = new CreateRunnable(aMainEventTarget);
-      if (aMainEventTarget) {
-        MOZ_ALWAYS_SUCCEEDS(
-            aMainEventTarget->Dispatch(gCreateRunnable, NS_DISPATCH_NORMAL));
-      } else {
-        MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(gCreateRunnable));
-      }
-    }
-
-    gCreateRunnable->AddCallback(aCallback);
-  }
+    RefPtr<QuotaManager> manager = new QuotaManager();
+
+    nsresult rv = manager->Init(gBaseDirPath);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      gCreateFailed = true;
+    } else {
+      gInstance = manager;
+    }
+  }
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(aCallback));
 }
 
 // static
 QuotaManager* QuotaManager::Get() {
   // Does not return an owning reference.
   return gInstance;
 }
 
 // static
 bool QuotaManager::IsShuttingDown() { return gShutdown; }
 
 // static
+void QuotaManager::ShutdownInstance() {
+  AssertIsOnBackgroundThread();
+
+  if (gInstance) {
+    gInstance->Shutdown();
+
+    gInstance = nullptr;
+  }
+
+  RefPtr<Runnable> runnable =
+      NS_NewRunnableFunction("dom::quota::QuotaManager::ShutdownCompleted",
+                             []() { Observer::ShutdownCompleted(); });
+  MOZ_ASSERT(runnable);
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
+}
+
+// static
 bool QuotaManager::IsOSMetadata(const nsAString& aFileName) {
   return aFileName.EqualsLiteral(DSSTORE_FILE_NAME) ||
          aFileName.EqualsLiteral(DESKTOP_FILE_NAME) ||
          aFileName.LowerCaseEqualsLiteral(DESKTOP_INI_FILE_NAME) ||
          aFileName.EqualsLiteral(THUMBS_DB_FILE_NAME);
 }
 
 // static
@@ -3229,17 +3151,17 @@ nsresult QuotaManager::Init(const nsAStr
   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 (CachedNextGenLocalStorageEnabled()) {
+  if (NextGenLocalStorageEnabled()) {
     mClients.AppendElement(localstorage::CreateQuotaClient());
   } else {
     mClients.SetLength(Client::TypeMax());
   }
 
   return NS_OK;
 }
 
@@ -5349,16 +5271,119 @@ void QuotaManager::GetStorageId(Persiste
   str.Append(aOrigin);
   str.Append('*');
   str.AppendInt(aClientType);
 
   aDatabaseId = str;
 }
 
 // static
+bool QuotaManager::IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo) {
+  switch (aPrincipalInfo.type()) {
+    // A system principal is acceptable.
+    case PrincipalInfo::TSystemPrincipalInfo: {
+      return true;
+    }
+
+    // Validate content principals to ensure that the spec, originNoSuffix and
+    // baseDomain are sane.
+    case PrincipalInfo::TContentPrincipalInfo: {
+      const ContentPrincipalInfo& info =
+          aPrincipalInfo.get_ContentPrincipalInfo();
+
+      // Verify the principal spec parses.
+      RefPtr<MozURL> specURL;
+      nsresult rv = MozURL::Init(getter_AddRefs(specURL), info.spec());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      // Verify the principal originNoSuffix matches spec.
+      nsCString originNoSuffix;
+      specURL->Origin(originNoSuffix);
+
+      if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
+        QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
+                   originNoSuffix.get(), info.originNoSuffix().get());
+        return false;
+      }
+
+      if (NS_WARN_IF(info.originNoSuffix().EqualsLiteral(kChromeOrigin))) {
+        return false;
+      }
+
+      // Verify the principal baseDomain exists.
+      if (NS_WARN_IF(info.baseDomain().IsVoid())) {
+        return false;
+      }
+
+      // Verify the principal baseDomain matches spec.
+      nsCString baseDomain;
+      rv = specURL->BaseDomain(baseDomain);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      if (NS_WARN_IF(baseDomain != info.baseDomain())) {
+        QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
+                   baseDomain.get(), info.baseDomain().get());
+        return false;
+      }
+
+      return true;
+    }
+
+    default: { break; }
+  }
+
+  // Null and expanded principals are not acceptable.
+  return false;
+}
+
+// static
+void QuotaManager::GetInfoFromValidatedPrincipalInfo(
+    const PrincipalInfo& aPrincipalInfo, nsACString* aSuffix,
+    nsACString* aGroup, nsACString* aOrigin) {
+  MOZ_ASSERT(IsPrincipalInfoValid(aPrincipalInfo));
+
+  switch (aPrincipalInfo.type()) {
+    case PrincipalInfo::TSystemPrincipalInfo: {
+      GetInfoForChrome(aSuffix, aGroup, aOrigin);
+      return;
+    }
+
+    case PrincipalInfo::TContentPrincipalInfo: {
+      const ContentPrincipalInfo& info =
+          aPrincipalInfo.get_ContentPrincipalInfo();
+
+      nsCString suffix;
+      info.attrs().CreateSuffix(suffix);
+
+      if (aSuffix) {
+        aSuffix->Assign(suffix);
+      }
+
+      if (aGroup) {
+        aGroup->Assign(info.baseDomain() + suffix);
+      }
+
+      if (aOrigin) {
+        aOrigin->Assign(info.originNoSuffix() + suffix);
+      }
+
+      return;
+    }
+
+    default: { break; }
+  }
+
+  MOZ_CRASH("Should never get here!");
+}
+
+// static
 nsresult QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
                                             nsACString* aSuffix,
                                             nsACString* aGroup,
                                             nsACString* aOrigin) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aPrincipal);
 
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
@@ -5385,38 +5410,21 @@ nsresult QuotaManager::GetInfoFromPrinci
 
   if (aSuffix) {
     aSuffix->Assign(suffix);
   }
 
   if (aGroup) {
     nsCString baseDomain;
     rv = aPrincipal->GetBaseDomain(baseDomain);
-    if (NS_FAILED(rv)) {
-      // A hack for JetPack.
-
-      nsCOMPtr<nsIURI> uri;
-      rv = aPrincipal->GetURI(getter_AddRefs(uri));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      bool isIndexedDBURI = false;
-      rv = uri->SchemeIs("indexedDB", &isIndexedDBURI);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (isIndexedDBURI) {
-        rv = NS_OK;
-      }
-    }
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (baseDomain.IsEmpty()) {
-      aGroup->Assign(origin);
-    } else {
-      aGroup->Assign(baseDomain + suffix);
-    }
+    MOZ_ASSERT(!baseDomain.IsEmpty());
+
+    aGroup->Assign(baseDomain + suffix);
   }
 
   if (aOrigin) {
     aOrigin->Assign(origin);
   }
 
   return NS_OK;
 }
@@ -5439,19 +5447,16 @@ nsresult QuotaManager::GetInfoFromWindow
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
 void QuotaManager::GetInfoForChrome(nsACString* aSuffix, nsACString* aGroup,
                                     nsACString* aOrigin) {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode());
-
   if (aSuffix) {
     aSuffix->Assign(EmptyCString());
   }
   if (aGroup) {
     ChromeOrigin(*aGroup);
   }
   if (aOrigin) {
     ChromeOrigin(*aOrigin);
@@ -6010,26 +6015,16 @@ OriginOperationBase::Run() {
   nsresult rv;
 
   switch (mState) {
     case State_Initial: {
       rv = Init();
       break;
     }
 
-    case State_Initializing: {
-      rv = InitOnMainThread();
-      break;
-    }
-
-    case State_FinishingInit: {
-      rv = FinishInit();
-      break;
-    }
-
     case State_CreatingQuotaManager: {
       rv = QuotaManagerOpen();
       break;
     }
 
     case State_DirectoryOpenPending: {
       rv = DirectoryOpen();
       break;
@@ -6087,48 +6082,16 @@ void OriginOperationBase::Finish(nsresul
 
   MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
 }
 
 nsresult OriginOperationBase::Init() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_Initial);
 
-  AdvanceState();
-
-  if (mNeedsMainThreadInit) {
-    MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-  } else {
-    AdvanceState();
-    MOZ_ALWAYS_SUCCEEDS(Run());
-  }
-
-  return NS_OK;
-}
-
-nsresult OriginOperationBase::InitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_Initializing);
-
-  nsresult rv = DoInitOnMainThread();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  AdvanceState();
-
-  MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
-
-  return NS_OK;
-}
-
-nsresult OriginOperationBase::FinishInit() {
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State_FinishingInit);
-
   if (QuotaManager::IsShuttingDown()) {
     return NS_ERROR_FAILURE;
   }
 
   AdvanceState();
 
   if (mNeedsQuotaManagerInit && !QuotaManager::Get()) {
     QuotaManager::GetOrCreate(this);
@@ -6363,29 +6326,159 @@ void Quota::StartIdleMaintenance() {
   QuotaManager* quotaManager = QuotaManager::Get();
   if (NS_WARN_IF(!quotaManager)) {
     return;
   }
 
   quotaManager->StartIdleMaintenance();
 }
 
+bool Quota::VerifyRequestParams(const UsageRequestParams& aParams) const {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
+
+  switch (aParams.type()) {
+    case UsageRequestParams::TAllUsageParams:
+      break;
+
+    case UsageRequestParams::TOriginUsageParams: {
+      const OriginUsageParams& params = aParams.get_OriginUsageParams();
+
+      if (NS_WARN_IF(
+              !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
+bool Quota::VerifyRequestParams(const RequestParams& aParams) const {
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParams.type() != RequestParams::T__None);
+
+  switch (aParams.type()) {
+    case RequestParams::TInitParams:
+    case RequestParams::TInitTemporaryStorageParams:
+      break;
+
+    case RequestParams::TInitOriginParams: {
+      const InitOriginParams& params = aParams.get_InitOriginParams();
+
+      if (NS_WARN_IF(
+              !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case RequestParams::TClearOriginParams: {
+      const ClearResetOriginParams& params =
+          aParams.get_ClearOriginParams().commonParams();
+
+      if (NS_WARN_IF(
+              !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case RequestParams::TResetOriginParams: {
+      const ClearResetOriginParams& params =
+          aParams.get_ResetOriginParams().commonParams();
+
+      if (NS_WARN_IF(
+              !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case RequestParams::TClearDataParams: {
+      if (BackgroundParent::IsOtherProcessActor(Manager())) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case RequestParams::TClearAllParams:
+    case RequestParams::TResetAllParams:
+      break;
+
+    case RequestParams::TPersistedParams: {
+      const PersistedParams& params = aParams.get_PersistedParams();
+
+      if (NS_WARN_IF(
+              !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    case RequestParams::TPersistParams: {
+      const PersistParams& params = aParams.get_PersistParams();
+
+      if (NS_WARN_IF(
+              !QuotaManager::IsPrincipalInfoValid(params.principalInfo()))) {
+        ASSERT_UNLESS_FUZZING();
+        return false;
+      }
+
+      break;
+    }
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return true;
+}
+
 void Quota::ActorDestroy(ActorDestroyReason aWhy) {
   AssertIsOnBackgroundThread();
 #ifdef DEBUG
   MOZ_ASSERT(!mActorDestroyed);
   mActorDestroyed = true;
 #endif
 }
 
 PQuotaUsageRequestParent* Quota::AllocPQuotaUsageRequestParent(
     const UsageRequestParams& aParams) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aParams.type() != UsageRequestParams::T__None);
 
+#ifdef DEBUG
+  // Always verify parameters in DEBUG builds!
+  bool trustParams = false;
+#else
+  bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
+#endif
+
+  if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
   RefPtr<QuotaUsageRequestBase> actor;
 
   switch (aParams.type()) {
     case UsageRequestParams::TAllUsageParams:
       actor = new GetUsageOp(aParams);
       break;
 
     case UsageRequestParams::TOriginUsageParams:
@@ -6428,24 +6521,26 @@ bool Quota::DeallocPQuotaUsageRequestPar
   return true;
 }
 
 PQuotaRequestParent* Quota::AllocPQuotaRequestParent(
     const RequestParams& aParams) {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aParams.type() != RequestParams::T__None);
 
-  if (aParams.type() == RequestParams::TClearDataParams) {
-    PBackgroundParent* actor = Manager();
-    MOZ_ASSERT(actor);
-
-    if (BackgroundParent::IsOtherProcessActor(actor)) {
-      ASSERT_UNLESS_FUZZING();
-      return nullptr;
-    }
+#ifdef DEBUG
+  // Always verify parameters in DEBUG builds!
+  bool trustParams = false;
+#else
+  bool trustParams = !BackgroundParent::IsOtherProcessActor(Manager());
+#endif
+
+  if (!trustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
   }
 
   RefPtr<QuotaRequestBase> actor;
 
   switch (aParams.type()) {
     case RequestParams::TInitParams:
       actor = new InitOp();
       break;
@@ -6569,16 +6664,42 @@ mozilla::ipc::IPCResult Quota::RecvStopI
     return IPC_OK();
   }
 
   quotaManager->StopIdleMaintenance();
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult Quota::RecvAbortOperationsForProcess(
+    const ContentParentId& aContentParentId) {
+  AssertIsOnBackgroundThread();
+
+  PBackgroundParent* actor = Manager();
+  MOZ_ASSERT(actor);
+
+  if (BackgroundParent::IsOtherProcessActor(actor)) {
+    ASSERT_UNLESS_FUZZING();
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  if (QuotaManager::IsShuttingDown()) {
+    return IPC_OK();
+  }
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  if (!quotaManager) {
+    return IPC_OK();
+  }
+
+  quotaManager->AbortOperationsForProcess(aContentParentId);
+
+  return IPC_OK();
+}
+
 bool QuotaUsageRequestBase::Init(Quota* aQuota) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aQuota);
 
   mNeedsQuotaManagerInit = true;
 
   return true;
 }
@@ -6887,48 +7008,26 @@ GetOriginUsageOp::GetOriginUsageOp(const
 bool GetOriginUsageOp::Init(Quota* aQuota) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aQuota);
 
   if (NS_WARN_IF(!QuotaUsageRequestBase::Init(aQuota))) {
     return false;
   }
 
-  mNeedsMainThreadInit = true;
+  // Figure out which origin we're dealing with.
+  nsCString origin;
+  QuotaManager::GetInfoFromValidatedPrincipalInfo(mParams.principalInfo(),
+                                                  &mSuffix, &mGroup, &origin);
+
+  mOriginScope.SetFromOrigin(origin);
 
   return true;
 }
 
-nsresult GetOriginUsageOp::DoInitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(GetState() == State_Initializing);
-  MOZ_ASSERT(mNeedsMainThreadInit);
-
-  const PrincipalInfo& principalInfo = mParams.principalInfo();
-
-  nsresult rv;
-  nsCOMPtr<nsIPrincipal> principal =
-      PrincipalInfoToPrincipal(principalInfo, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Figure out which origin we're dealing with.
-  nsCString origin;
-  rv =
-      QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  mOriginScope.SetFromOrigin(origin);
-
-  return NS_OK;
-}
-
 nsresult GetOriginUsageOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
   AssertIsOnIOThread();
   MOZ_ASSERT(mUsageInfo.TotalUsage() == 0);
 
   AUTO_PROFILER_LABEL("GetOriginUsageOp::DoDirectoryWork", OTHER);
 
   nsresult rv;
 
@@ -7073,48 +7172,26 @@ bool InitOriginOp::Init(Quota* aQuota) {
   if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
     return false;
   }
 
   MOZ_ASSERT(mParams.persistenceType() != PERSISTENCE_TYPE_INVALID);
 
   mPersistenceType.SetValue(mParams.persistenceType());
 
-  mNeedsMainThreadInit = true;
+  // Figure out which origin we're dealing with.
+  nsCString origin;
+  QuotaManager::GetInfoFromValidatedPrincipalInfo(mParams.principalInfo(),
+                                                  &mSuffix, &mGroup, &origin);
+
+  mOriginScope.SetFromOrigin(origin);
 
   return true;
 }
 
-nsresult InitOriginOp::DoInitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(GetState() == State_Initializing);
-  MOZ_ASSERT(mNeedsMainThreadInit);
-
-  const PrincipalInfo& principalInfo = mParams.principalInfo();
-
-  nsresult rv;
-  nsCOMPtr<nsIPrincipal> principal =
-      PrincipalInfoToPrincipal(principalInfo, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Figure out which origin we're dealing with.
-  nsCString origin;
-  rv =
-      QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  mOriginScope.SetFromOrigin(origin);
-
-  return NS_OK;
-}
-
 nsresult InitOriginOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
   AssertIsOnIOThread();
   MOZ_ASSERT(!mPersistenceType.IsNull());
 
   AUTO_PROFILER_LABEL("InitOriginOp::DoDirectoryWork", OTHER);
 
   nsCOMPtr<nsIFile> directory;
   bool created;
@@ -7406,55 +7483,34 @@ bool ClearOriginOp::Init(Quota* aQuota) 
   }
 
   if (mParams.persistenceTypeIsExplicit()) {
     MOZ_ASSERT(mParams.persistenceType() != PERSISTENCE_TYPE_INVALID);
 
     mPersistenceType.SetValue(mParams.persistenceType());
   }
 
-  if (mParams.clientTypeIsExplicit()) {
-    MOZ_ASSERT(mParams.clientType() != Client::TYPE_MAX);
-
-    mClientType.SetValue(mParams.clientType());
-  }
-
-  mNeedsMainThreadInit = true;
-
-  return true;
-}
-
-nsresult ClearOriginOp::DoInitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(GetState() == State_Initializing);
-  MOZ_ASSERT(mNeedsMainThreadInit);
-
-  const PrincipalInfo& principalInfo = mParams.principalInfo();
-
-  nsresult rv;
-  nsCOMPtr<nsIPrincipal> principal =
-      PrincipalInfoToPrincipal(principalInfo, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
   // Figure out which origin we're dealing with.
   nsCString origin;
-  rv = QuotaManager::GetInfoFromPrincipal(principal, nullptr, nullptr, &origin);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+  QuotaManager::GetInfoFromValidatedPrincipalInfo(mParams.principalInfo(),
+                                                  nullptr, nullptr, &origin);
 
   if (mParams.matchAll()) {
     mOriginScope.SetFromPrefix(origin);
   } else {
     mOriginScope.SetFromOrigin(origin);
   }
 
-  return NS_OK;
+  if (mParams.clientTypeIsExplicit()) {
+    MOZ_ASSERT(mParams.clientType() != Client::TYPE_MAX);
+
+    mClientType.SetValue(mParams.clientType());
+  }
+
+  return true;
 }
 
 void ClearOriginOp::GetResponse(RequestResponse& aResponse) {
   AssertIsOnOwningThread();
 
   if (mClear) {
     aResponse = ClearOriginResponse();
   } else {
@@ -7472,31 +7528,21 @@ ClearDataOp::ClearDataOp(const RequestPa
 bool ClearDataOp::Init(Quota* aQuota) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aQuota);
 
   if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
     return false;
   }
 
-  mNeedsMainThreadInit = true;
+  mOriginScope.SetFromPattern(mParams.pattern());
 
   return true;
 }
 
-nsresult ClearDataOp::DoInitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(GetState() == State_Initializing);
-  MOZ_ASSERT(mNeedsMainThreadInit);
-
-  mOriginScope.SetFromJSONPattern(mParams.pattern());
-
-  return NS_OK;
-}
-
 void ClearDataOp::GetResponse(RequestResponse& aResponse) {
   AssertIsOnOwningThread();
 
   aResponse = ClearDataResponse();
 }
 
 PersistRequestBase::PersistRequestBase(const PrincipalInfo& aPrincipalInfo)
     : QuotaRequestBase(/* aExclusive */ false), mPrincipalInfo(aPrincipalInfo) {
@@ -7508,46 +7554,26 @@ bool PersistRequestBase::Init(Quota* aQu
   MOZ_ASSERT(aQuota);
 
   if (NS_WARN_IF(!QuotaRequestBase::Init(aQuota))) {
     return false;
   }
 
   mPersistenceType.SetValue(PERSISTENCE_TYPE_DEFAULT);
 
-  mNeedsMainThreadInit = true;
+  // Figure out which origin we're dealing with.
+  nsCString origin;
+  QuotaManager::GetInfoFromValidatedPrincipalInfo(mPrincipalInfo, &mSuffix,
+                                                  &mGroup, &origin);
+
+  mOriginScope.SetFromOrigin(origin);
 
   return true;
 }
 
-nsresult PersistRequestBase::DoInitOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(GetState() == State_Initializing);
-  MOZ_ASSERT(mNeedsMainThreadInit);
-
-  nsresult rv;
-  nsCOMPtr<nsIPrincipal> principal =
-      PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  // Figure out which origin we're dealing with.
-  nsCString origin;
-  rv =
-      QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup, &origin);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  mOriginScope.SetFromOrigin(origin);
-
-  return NS_OK;
-}
-
 PersistedOp::PersistedOp(const RequestParams& aParams)
     : PersistRequestBase(aParams.get_PersistedParams().principalInfo()),
       mPersisted(false) {
   MOZ_ASSERT(aParams.type() == RequestParams::TPersistedParams);
 }
 
 nsresult PersistedOp::DoDirectoryWork(QuotaManager* aQuotaManager) {
   AssertIsOnIOThread();
@@ -7714,16 +7740,100 @@ nsresult PersistOp::DoDirectoryWork(Quot
 }
 
 void PersistOp::GetResponse(RequestResponse& aResponse) {
   AssertIsOnOwningThread();
 
   aResponse = PersistResponse();
 }
 
+// static
+already_AddRefed<PrincipalVerifier> PrincipalVerifier::CreateAndDispatch(
+    nsTArray<PrincipalInfo>&& aPrincipalInfos) {
+  AssertIsOnIOThread();
+
+  RefPtr<PrincipalVerifier> verifier =
+      new PrincipalVerifier(std::move(aPrincipalInfos));
+
+  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(verifier));
+
+  return verifier.forget();
+}
+
+bool PrincipalVerifier::IsPrincipalInfoValid(
+    const PrincipalInfo& aPrincipalInfo) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  switch (aPrincipalInfo.type()) {
+    // A system principal is acceptable.
+    case PrincipalInfo::TSystemPrincipalInfo: {
+      return true;
+    }
+
+    case PrincipalInfo::TContentPrincipalInfo: {
+      const ContentPrincipalInfo& info =
+          aPrincipalInfo.get_ContentPrincipalInfo();
+
+      nsCOMPtr<nsIURI> uri;
+      nsresult rv = NS_NewURI(getter_AddRefs(uri), info.spec());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      nsCOMPtr<nsIPrincipal> principal =
+          BasePrincipal::CreateCodebasePrincipal(uri, info.attrs());
+      if (NS_WARN_IF(!principal)) {
+        return false;
+      }
+
+      nsCString originNoSuffix;
+      rv = principal->GetOriginNoSuffix(originNoSuffix);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      if (NS_WARN_IF(originNoSuffix != info.originNoSuffix())) {
+        QM_WARNING("originNoSuffix (%s) doesn't match passed one (%s)!",
+                   originNoSuffix.get(), info.originNoSuffix().get());
+        return false;
+      }
+
+      nsCString baseDomain;
+      rv = principal->GetBaseDomain(baseDomain);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      if (NS_WARN_IF(baseDomain != info.baseDomain())) {
+        QM_WARNING("baseDomain (%s) doesn't match passed one (%s)!",
+                   baseDomain.get(), info.baseDomain().get());
+        return false;
+      }
+
+      return true;
+    }
+
+    default: { break; }
+  }
+
+  // Null and expanded principals are not acceptable.
+  return false;
+}
+
+NS_IMETHODIMP
+PrincipalVerifier::Run() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  for (auto& principalInfo : mPrincipalInfos) {
+    MOZ_DIAGNOSTIC_ASSERT(IsPrincipalInfoValid(principalInfo));
+  }
+
+  return NS_OK;
+}
+
 nsresult StorageOperationBase::GetDirectoryMetadata(nsIFile* aDirectory,
                                                     int64_t& aTimestamp,
                                                     nsACString& aGroup,
                                                     nsACString& aOrigin,
                                                     Nullable<bool>& aIsApp) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
 
@@ -7853,37 +7963,76 @@ nsresult StorageOperationBase::RemoveObs
 
   return NS_OK;
 }
 
 nsresult StorageOperationBase::ProcessOriginDirectories() {
   AssertIsOnIOThread();
   MOZ_ASSERT(!mOriginProps.IsEmpty());
 
-  MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
-
-  {
-    mozilla::MutexAutoLock autolock(mMutex);
-    while (mWaiting) {
-      mCondVar.Wait();
-    }
-  }
-
-  if (NS_WARN_IF(NS_FAILED(mMainThreadResultCode))) {
-    return mMainThreadResultCode;
-  }
-
-  // Verify that the bounce to the main thread didn't start the shutdown
-  // sequence.
-  if (NS_WARN_IF(QuotaManager::IsShuttingDown())) {
-    return NS_ERROR_FAILURE;
-  }
-
   nsresult rv;
 
+  nsTArray<PrincipalInfo> principalInfos;
+
+  for (auto& originProps : mOriginProps) {
+    switch (originProps.mType) {
+      case OriginProps::eChrome: {
+        QuotaManager::GetInfoForChrome(
+            &originProps.mSuffix, &originProps.mGroup, &originProps.mOrigin);
+        break;
+      }
+
+      case OriginProps::eContent: {
+        RefPtr<MozURL> specURL;
+        nsresult rv = MozURL::Init(getter_AddRefs(specURL), originProps.mSpec);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        nsCString originNoSuffix;
+        specURL->Origin(originNoSuffix);
+
+        nsCString baseDomain;
+        rv = specURL->BaseDomain(baseDomain);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return rv;
+        }
+
+        ContentPrincipalInfo contentPrincipalInfo;
+        contentPrincipalInfo.attrs() = originProps.mAttrs;
+        contentPrincipalInfo.originNoSuffix() = originNoSuffix;
+        contentPrincipalInfo.spec() = originProps.mSpec;
+        contentPrincipalInfo.baseDomain() = baseDomain;
+
+        PrincipalInfo principalInfo(contentPrincipalInfo);
+
+        QuotaManager::GetInfoFromValidatedPrincipalInfo(
+            principalInfo, &originProps.mSuffix, &originProps.mGroup,
+            &originProps.mOrigin);
+
+        principalInfos.AppendElement(principalInfo);
+
+        break;
+      }
+
+      case OriginProps::eObsolete: {
+        // There's no way to get info for obsolete origins.
+        break;
+      }
+
+      default:
+        MOZ_CRASH("Bad type!");
+    }
+  }
+
+  if (!principalInfos.IsEmpty()) {
+    RefPtr<PrincipalVerifier> principalVerifier =
+        PrincipalVerifier::CreateAndDispatch(std::move(principalInfos));
+  }
+
   // Don't try to upgrade obsolete origins, remove them right after we detect
   // them.
   for (auto& originProps : mOriginProps) {
     if (originProps.mType == OriginProps::eObsolete) {
       MOZ_ASSERT(originProps.mSuffix.IsEmpty());
       MOZ_ASSERT(originProps.mGroup.IsEmpty());
       MOZ_ASSERT(originProps.mOrigin.IsEmpty());
 
@@ -7897,87 +8046,16 @@ nsresult StorageOperationBase::ProcessOr
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
-nsresult StorageOperationBase::RunOnMainThread() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mOriginProps.IsEmpty());
-
-  nsresult rv;
-
-  for (uint32_t count = mOriginProps.Length(), index = 0; index < count;
-       index++) {
-    OriginProps& originProps = mOriginProps[index];
-
-    switch (originProps.mType) {
-      case OriginProps::eChrome: {
-        QuotaManager::GetInfoForChrome(
-            &originProps.mSuffix, &originProps.mGroup, &originProps.mOrigin);
-        break;
-      }
-
-      case OriginProps::eContent: {
-        nsCOMPtr<nsIURI> uri;
-        rv = NS_NewURI(getter_AddRefs(uri), originProps.mSpec);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-
-        nsCOMPtr<nsIPrincipal> principal =
-            BasePrincipal::CreateCodebasePrincipal(uri, originProps.mAttrs);
-        if (NS_WARN_IF(!principal)) {
-          return NS_ERROR_FAILURE;
-        }
-
-        rv = QuotaManager::GetInfoFromPrincipal(principal, &originProps.mSuffix,
-                                                &originProps.mGroup,
-                                                &originProps.mOrigin);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
-
-        break;
-      }
-
-      case OriginProps::eObsolete: {
-        // There's no way to get info for obsolete origins.
-        break;
-      }
-
-      default:
-        MOZ_CRASH("Bad type!");
-    }
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-StorageOperationBase::Run() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsresult rv = RunOnMainThread();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mMainThreadResultCode = rv;
-  }
-
-  MutexAutoLock lock(mMutex);
-  MOZ_ASSERT(mWaiting);
-
-  mWaiting = false;
-  mCondVar.Notify();
-
-  return NS_OK;
-}
-
 nsresult StorageOperationBase::OriginProps::Init(nsIFile* aDirectory) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
 
   nsString leafName;
   nsresult rv = aDirectory->GetLeafName(leafName);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
--- a/dom/quota/ActorsParent.h
+++ b/dom/quota/ActorsParent.h
@@ -8,17 +8,21 @@
 #define mozilla_dom_quota_ActorsParent_h
 
 namespace mozilla {
 namespace dom {
 namespace quota {
 
 class PQuotaParent;
 
+void InitializeQuotaManager();
+
 PQuotaParent* AllocPQuotaParent();
 
 bool DeallocPQuotaParent(PQuotaParent* aActor);
 
+bool RecvShutdownQuotaManager();
+
 }  // namespace quota
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_quota_ActorsParent_h
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -167,20 +167,15 @@ class Client {
   virtual void AbortOperationsForProcess(ContentParentId aContentParentId) = 0;
 
   virtual void StartIdleMaintenance() = 0;
 
   virtual void StopIdleMaintenance() = 0;
 
   virtual void ShutdownWorkThreads() = 0;
 
-  // Methods which are called on the main thread.
-  virtual void DidInitialize(QuotaManager* aQuotaManager) {}
-
-  virtual void WillShutdown() {}
-
  protected:
   virtual ~Client() {}
 };
 
 END_QUOTA_NAMESPACE
 
 #endif  // mozilla_dom_quota_client_h__
--- a/dom/quota/PQuota.ipdl
+++ b/dom/quota/PQuota.ipdl
@@ -5,22 +5,28 @@
 include protocol PBackground;
 include protocol PQuotaRequest;
 include protocol PQuotaUsageRequest;
 
 include PBackgroundSharedTypes;
 
 include "mozilla/dom/quota/SerializationHelpers.h";
 
+using mozilla::OriginAttributesPattern
+  from "mozilla/OriginAttributes.h";
+
 using mozilla::dom::quota::PersistenceType
   from "mozilla/dom/quota/PersistenceType.h";
 
 using mozilla::dom::quota::Client::Type
   from "mozilla/dom/quota/Client.h";
 
+using mozilla::dom::ContentParentId
+  from "mozilla/dom/ipc/IdType.h";
+
 namespace mozilla {
 namespace dom {
 namespace quota {
 
 struct InitParams
 {
 };
 
@@ -68,17 +74,17 @@ struct ClearOriginParams
 
 struct ResetOriginParams
 {
   ClearResetOriginParams commonParams;
 };
 
 struct ClearDataParams
 {
-  nsString pattern;
+  OriginAttributesPattern pattern;
 };
 
 struct ClearAllParams
 {
 };
 
 struct ResetAllParams
 {
@@ -120,13 +126,15 @@ parent:
 
   async PQuotaUsageRequest(UsageRequestParams params);
 
   async PQuotaRequest(RequestParams params);
 
   async StartIdleMaintenance();
 
   async StopIdleMaintenance();
+
+  async AbortOperationsForProcess(ContentParentId contentParentId);
 };
 
 } // namespace quota
 } // namespace dom
 } // namespace mozilla
--- a/dom/quota/QuotaManager.h
+++ b/dom/quota/QuotaManager.h
@@ -31,16 +31,22 @@ class nsITimer;
 class nsIURI;
 class nsPIDOMWindowOuter;
 class nsIRunnable;
 
 namespace mozilla {
 
 class OriginAttributes;
 
+namespace ipc {
+
+class PrincipalInfo;
+
+}  // namespace ipc
+
 }  // namespace mozilla
 
 BEGIN_QUOTA_NAMESPACE
 
 class DirectoryLockImpl;
 class GroupInfo;
 class GroupInfoPair;
 class OriginInfo;
@@ -80,29 +86,27 @@ struct OriginParams {
 };
 
 class QuotaManager final : public BackgroundThreadObject {
   friend class DirectoryLockImpl;
   friend class GroupInfo;
   friend class OriginInfo;
   friend class QuotaObject;
 
+  typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
   typedef nsClassHashtable<nsCStringHashKey, nsTArray<DirectoryLockImpl*>>
       DirectoryLockTable;
 
- public:
-  class CreateRunnable;
-
- private:
-  class ShutdownRunnable;
-  class ShutdownObserver;
+  class Observer;
 
  public:
   NS_INLINE_DECL_REFCOUNTING(QuotaManager)
 
+  static nsresult Initialize();
+
   static bool IsRunningXPCShellTests() {
     static bool kRunningXPCShellTests =
         !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR");
     return kRunningXPCShellTests;
   }
 
   static bool IsRunningGTests() {
     static bool kRunningGTests = !!PR_GetEnv("MOZ_RUN_GTEST");
@@ -115,16 +119,18 @@ class QuotaManager final : public Backgr
                           nsIEventTarget* aMainEventTarget = nullptr);
 
   // Returns a non-owning reference.
   static QuotaManager* Get();
 
   // Returns true if we've begun the shutdown process.
   static bool IsShuttingDown();
 
+  static void ShutdownInstance();
+
   static bool IsOSMetadata(const nsAString& aFileName);
 
   static bool IsDotFile(const nsAString& aFileName);
 
   bool IsOriginInitialized(const nsACString& aOrigin) const {
     AssertIsOnIOThread();
 
     return mInitializedOrigins.Contains(aOrigin);
@@ -322,16 +328,22 @@ class QuotaManager final : public Backgr
   void GetGroupUsageAndLimit(const nsACString& aGroup, UsageInfo* aUsageInfo);
 
   void NotifyStoragePressure(uint64_t aUsage);
 
   static void GetStorageId(PersistenceType aPersistenceType,
                            const nsACString& aOrigin, Client::Type aClientType,
                            nsACString& aDatabaseId);
 
+  static bool IsPrincipalInfoValid(const PrincipalInfo& aPrincipalInfo);
+
+  static void GetInfoFromValidatedPrincipalInfo(
+      const PrincipalInfo& aPrincipalInfo, nsACString* aSuffix,
+      nsACString* aGroup, nsACString* aOrigin);
+
   static nsresult GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
                                        nsACString* aSuffix, nsACString* aGroup,
                                        nsACString* aOrigin);
 
   static nsresult GetInfoFromWindow(nsPIDOMWindowOuter* aWindow,
                                     nsACString* aSuffix, nsACString* aGroup,
                                     nsACString* aOrigin);
 
--- a/dom/quota/QuotaManagerService.cpp
+++ b/dom/quota/QuotaManagerService.cpp
@@ -60,16 +60,20 @@ nsresult CheckedPrincipalToPrincipalInfo
                                          PrincipalInfo& aPrincipalInfo) {
   MOZ_ASSERT(aPrincipal);
 
   nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
+    return NS_ERROR_FAILURE;
+  }
+
   if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
       aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
@@ -112,28 +116,16 @@ nsresult GetClearResetOriginParams(nsIPr
     aParams.clientTypeIsExplicit() = true;
   }
 
   aParams.matchAll() = aMatchAll;
 
   return NS_OK;
 }
 
-class AbortOperationsRunnable final : public Runnable {
-  ContentParentId mContentParentId;
-
- public:
-  explicit AbortOperationsRunnable(ContentParentId aContentParentId)
-      : Runnable("dom::quota::AbortOperationsRunnable"),
-        mContentParentId(aContentParentId) {}
-
- private:
-  NS_DECL_NSIRUNNABLE
-};
-
 }  // namespace
 
 class QuotaManagerService::PendingRequestInfo {
  protected:
   RefPtr<RequestBase> mRequest;
 
  public:
   explicit PendingRequestInfo(RequestBase* aRequest) : mRequest(aRequest) {}
@@ -235,45 +227,30 @@ already_AddRefed<QuotaManagerService> Qu
 }
 
 void QuotaManagerService::ClearBackgroundActor() {
   MOZ_ASSERT(NS_IsMainThread());
 
   mBackgroundActor = nullptr;
 }
 
-void QuotaManagerService::NoteLiveManager(QuotaManager* aManager) {
-  MOZ_ASSERT(XRE_IsParentProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aManager);
-
-  mBackgroundThread = aManager->OwningThread();
-}
-
-void QuotaManagerService::NoteShuttingDownManager() {
-  MOZ_ASSERT(XRE_IsParentProcess());
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mBackgroundThread = nullptr;
-}
-
 void QuotaManagerService::AbortOperationsForProcess(
     ContentParentId aContentParentId) {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mBackgroundThread) {
+  nsresult rv = EnsureBackgroundActor();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
-  RefPtr<AbortOperationsRunnable> runnable =
-      new AbortOperationsRunnable(aContentParentId);
-
-  MOZ_ALWAYS_SUCCEEDS(
-      mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL));
+  if (NS_WARN_IF(
+          !mBackgroundActor->SendAbortOperationsForProcess(aContentParentId))) {
+    return;
+  }
 }
 
 nsresult QuotaManagerService::Init() {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (XRE_IsParentProcess()) {
     nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
@@ -301,18 +278,19 @@ void QuotaManagerService::Destroy() {
     MOZ_ASSERT(false, "Shutdown more than once?!");
   }
 
   Preferences::UnregisterCallback(TestingPrefChangedCallback, kTestingPref);
 
   delete this;
 }
 
-nsresult QuotaManagerService::InitiateRequest(
-    nsAutoPtr<PendingRequestInfo>& aInfo) {
+nsresult QuotaManagerService::EnsureBackgroundActor() {
+  MOZ_ASSERT(NS_IsMainThread());
+
   // Nothing can be done here if we have previously failed to create a
   // background actor.
   if (mBackgroundActorFailed) {
     return NS_ERROR_FAILURE;
   }
 
   if (!mBackgroundActor) {
     PBackgroundChild* backgroundActor =
@@ -330,18 +308,27 @@ nsresult QuotaManagerService::InitiateRe
     }
   }
 
   if (!mBackgroundActor) {
     mBackgroundActorFailed = true;
     return NS_ERROR_FAILURE;
   }
 
-  // If we already have a background actor then we can start this request now.
-  nsresult rv = aInfo->InitiateRequest(mBackgroundActor);
+  return NS_OK;
+}
+
+nsresult QuotaManagerService::InitiateRequest(
+    nsAutoPtr<PendingRequestInfo>& aInfo) {
+  nsresult rv = EnsureBackgroundActor();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aInfo->InitiateRequest(mBackgroundActor);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 void QuotaManagerService::PerformIdleMaintenance() {
@@ -581,21 +568,24 @@ QuotaManagerService::Clear(nsIQuotaReque
   return NS_OK;
 }
 
 NS_IMETHODIMP
 QuotaManagerService::ClearStoragesForOriginAttributesPattern(
     const nsAString& aPattern, nsIQuotaRequest** _retval) {
   MOZ_ASSERT(NS_IsMainThread());
 
+  OriginAttributesPattern pattern;
+  MOZ_ALWAYS_TRUE(pattern.Init(aPattern));
+
   RefPtr<Request> request = new Request();
 
   ClearDataParams params;
 
-  params.pattern() = aPattern;
+  params.pattern() = pattern;
 
   nsAutoPtr<PendingRequestInfo> info(new RequestInfo(request, params));
 
   nsresult rv = InitiateRequest(info);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -823,34 +813,16 @@ QuotaManagerService::Observe(nsISupports
   return NS_OK;
 }
 
 void QuotaManagerService::Notify(const hal::BatteryInformation& aBatteryInfo) {
   // This notification is received when battery data changes. We don't need to
   // deal with this notification.
 }
 
-NS_IMETHODIMP
-AbortOperationsRunnable::Run() {
-  AssertIsOnBackgroundThread();
-
-  if (QuotaManager::IsShuttingDown()) {
-    return NS_OK;
-  }
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  if (!quotaManager) {
-    return NS_OK;
-  }
-
-  quotaManager->AbortOperationsForProcess(mContentParentId);
-
-  return NS_OK;
-}
-
 nsresult QuotaManagerService::UsageRequestInfo::InitiateRequest(
     QuotaChild* aActor) {
   MOZ_ASSERT(aActor);
 
   auto request = static_cast<UsageRequest*>(mRequest.get());
 
   auto actor = new QuotaUsageRequestChild(request);
 
--- a/dom/quota/QuotaManagerService.h
+++ b/dom/quota/QuotaManagerService.h
@@ -39,18 +39,16 @@ class QuotaManagerService final : public
   typedef mozilla::ipc::PBackgroundChild PBackgroundChild;
 
   class BackgroundCreateCallback;
   class PendingRequestInfo;
   class UsageRequestInfo;
   class RequestInfo;
   class IdleMaintenanceInfo;
 
-  nsCOMPtr<nsIEventTarget> mBackgroundThread;
-
   QuotaChild* mBackgroundActor;
 
   bool mBackgroundActorFailed;
   bool mIdleObserverRegistered;
 
  public:
   // Returns a non-owning reference.
   static QuotaManagerService* GetOrCreate();
@@ -58,37 +56,31 @@ class QuotaManagerService final : public
   // Returns a non-owning reference.
   static QuotaManagerService* Get();
 
   // No one should call this but the factory.
   static already_AddRefed<QuotaManagerService> FactoryCreate();
 
   void ClearBackgroundActor();
 
-  void NoteLiveManager(QuotaManager* aManager);
-
-  void NoteShuttingDownManager();
-
   // Called when a process is being shot down. Aborts any running operations
   // for the given process.
   void AbortOperationsForProcess(ContentParentId aContentParentId);
 
  private:
   QuotaManagerService();
   ~QuotaManagerService();
 
   nsresult Init();
 
   void Destroy();
 
-  nsresult InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo);
+  nsresult EnsureBackgroundActor();
 
-  nsresult BackgroundActorCreated(PBackgroundChild* aBackgroundActor);
-
-  void BackgroundActorFailed();
+  nsresult InitiateRequest(nsAutoPtr<PendingRequestInfo>& aInfo);
 
   void PerformIdleMaintenance();
 
   void RemoveIdleObserver();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIQUOTAMANAGERSERVICE
   NS_DECL_NSIOBSERVER
new file mode 100644
--- /dev/null
+++ b/dom/quota/components.conf
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+Classes = [
+    {
+        'cid': '{b6f2f870-b0bc-4a1a-9c40-02cc171adb5b}',
+        'contract_ids': ['@mozilla.org/network/protocol;1?name=indexeddb'],
+        'type': 'nsIndexedDBProtocolHandler',
+        'headers': ['/dom/quota/nsIndexedDBProtocolHandler.h'],
+    },
+]
--- a/dom/quota/moz.build
+++ b/dom/quota/moz.build
@@ -44,21 +44,26 @@ EXPORTS.mozilla.dom.quota += [
     'QuotaCommon.h',
     'QuotaManager.h',
     'QuotaManagerService.h',
     'QuotaObject.h',
     'SerializationHelpers.h',
     'UsageInfo.h',
 ]
 
+XPCOM_MANIFESTS += [
+    'components.conf',
+]
+
 UNIFIED_SOURCES += [
     'ActorsChild.cpp',
     'ActorsParent.cpp',
     'FileStreams.cpp',
     'MemoryOutputStream.cpp',
+    'nsIndexedDBProtocolHandler.cpp',
     'QuotaCommon.cpp',
     'QuotaManagerService.cpp',
     'QuotaRequests.cpp',
     'QuotaResults.cpp',
     'StorageManager.cpp',
 ]
 
 IPDL_SOURCES += [
new file mode 100644
--- /dev/null
+++ b/dom/quota/nsIndexedDBProtocolHandler.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sts=2 sw=2 et cin:
+ *
+ * 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 "nsIndexedDBProtocolHandler.h"
+
+#include "nsIURIMutator.h"
+#include "nsStandardURL.h"
+
+using namespace mozilla::net;
+
+nsIndexedDBProtocolHandler::nsIndexedDBProtocolHandler() {}
+
+nsIndexedDBProtocolHandler::~nsIndexedDBProtocolHandler() {}
+
+NS_IMPL_ISUPPORTS(nsIndexedDBProtocolHandler, nsIProtocolHandler,
+                  nsISupportsWeakReference)
+
+NS_IMETHODIMP nsIndexedDBProtocolHandler::GetScheme(nsACString& aScheme) {
+  aScheme.AssignLiteral("indexeddb");
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsIndexedDBProtocolHandler::GetDefaultPort(
+    int32_t* aDefaultPort) {
+  *aDefaultPort = -1;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsIndexedDBProtocolHandler::GetProtocolFlags(
+    uint32_t* aProtocolFlags) {
+  *aProtocolFlags = URI_STD | URI_DANGEROUS_TO_LOAD | URI_DOES_NOT_RETURN_DATA |
+                    URI_NON_PERSISTABLE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP nsIndexedDBProtocolHandler::NewURI(
+    const nsACString& aSpec, const char* aOriginCharset, nsIURI* aBaseURI,
+    nsIURI** _retval) {
+  nsCOMPtr<nsIURI> baseURI(aBaseURI);
+  return NS_MutateURI(new nsStandardURL::Mutator())
+      .Apply(NS_MutatorMethod(&nsIStandardURLMutator::Init,
+                              nsIStandardURL::URLTYPE_AUTHORITY, 0,
+                              nsCString(aSpec), aOriginCharset, baseURI,
+                              nullptr))
+      .Finalize(_retval);
+}
+
+NS_IMETHODIMP
+nsIndexedDBProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+                                       nsIChannel** _retval) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsIndexedDBProtocolHandler::AllowPort(int32_t aPort, const char* aScheme,
+                                      bool* _retval) {
+  *_retval = false;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/quota/nsIndexedDBProtocolHandler.h
@@ -0,0 +1,25 @@
+/* -*- 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 nsIndexedDBProtocolHandler_h
+#define nsIndexedDBProtocolHandler_h
+
+#include "nsIProtocolHandler.h"
+#include "nsWeakReference.h"
+
+class nsIndexedDBProtocolHandler final : public nsIProtocolHandler,
+                                         public nsSupportsWeakReference {
+ public:
+  nsIndexedDBProtocolHandler();
+
+ private:
+  ~nsIndexedDBProtocolHandler();
+
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIPROTOCOLHANDLER
+};
+
+#endif  // nsIndexedDBProtocolHandler_h
--- a/dom/serviceworkers/ServiceWorkerRegistrar.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrar.cpp
@@ -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/. */
 
 #include "ServiceWorkerRegistrar.h"
 #include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/net/MozURL.h"
 
+#include "mozIThirdPartyUtil.h"
 #include "nsIEventTarget.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
 #include "nsIObserverService.h"
 #include "nsIOutputStream.h"
 #include "nsISafeOutputStream.h"
 #include "nsIServiceWorkerManager.h"
 
@@ -48,24 +49,31 @@ namespace {
 
 static const char* gSupportedRegistrarVersions[] = {
     SERVICEWORKERREGISTRAR_VERSION, "7", "6", "5", "4", "3", "2"};
 
 static const uint32_t kInvalidGeneration = static_cast<uint32_t>(-1);
 
 StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar;
 
-nsresult GetOrigin(const nsACString& aURL, nsACString& aOrigin) {
+nsresult GetOriginAndBaseDomain(const nsACString& aURL, nsACString& aOrigin,
+                                nsACString& aBaseDomain) {
   RefPtr<net::MozURL> url;
   nsresult rv = net::MozURL::Init(getter_AddRefs(url), aURL);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   url->Origin(aOrigin);
+
+  rv = url->BaseDomain(aBaseDomain);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   return NS_OK;
 }
 
 nsresult ReadLine(nsILineInputStream* aStream, nsACString& aValue) {
   bool hasMoreLines;
   nsresult rv = aStream->ReadLine(aValue, &hasMoreLines);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -101,25 +109,27 @@ nsresult CreatePrincipalInfo(nsILineInpu
   }
 
   rv = ReadLine(aStream, aEntry->scope());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCString origin;
-  rv = GetOrigin(aEntry->scope(), origin);
+  nsCString baseDomain;
+  rv = GetOriginAndBaseDomain(aEntry->scope(), origin, baseDomain);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // CSP will be applied during the script load.
   nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
   aEntry->principal() = mozilla::ipc::ContentPrincipalInfo(
-      attrs, origin, aEntry->scope(), Nothing(), std::move(policies));
+      attrs, origin, aEntry->scope(), Nothing(), std::move(policies),
+      baseDomain);
 
   return NS_OK;
 }
 
 }  // namespace
 
 NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar, nsIObserver, nsIAsyncShutdownBlocker)
 
--- a/dom/serviceworkers/test/gtest/TestReadWrite.cpp
+++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
@@ -270,17 +270,17 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
       reg.lastUpdateTime() = PR_Now();
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
 
       nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
       reg.principal() = mozilla::ipc::ContentPrincipalInfo(
           mozilla::OriginAttributes(i, i % 2), spec, spec, mozilla::Nothing(),
-          std::move(policies));
+          std::move(policies), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
@@ -860,17 +860,17 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
           nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write dedupe/%d", i);
 
       nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
       reg.principal() = mozilla::ipc::ContentPrincipalInfo(
           mozilla::OriginAttributes(0, false), spec, spec, mozilla::Nothing(),
-          std::move(policies));
+          std::move(policies), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
--- a/dom/simpledb/ActorsParent.cpp
+++ b/dom/simpledb/ActorsParent.cpp
@@ -492,16 +492,21 @@ PBackgroundSDBConnectionParent* AllocPBa
     return nullptr;
   }
 
   if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
+    ASSERT_UNLESS_FUZZING();
+    return nullptr;
+  }
+
   RefPtr<Connection> actor = new Connection(aPrincipalInfo);
 
   return actor.forget().take();
 }
 
 bool RecvPBackgroundSDBConnectionConstructor(
     PBackgroundSDBConnectionParent* aActor,
     const PrincipalInfo& aPrincipalInfo) {
--- a/dom/simpledb/SDBConnection.cpp
+++ b/dom/simpledb/SDBConnection.cpp
@@ -216,16 +216,20 @@ SDBConnection::Init(nsIPrincipal* aPrinc
   }
 
   if (principalInfo->type() != PrincipalInfo::TContentPrincipalInfo &&
       principalInfo->type() != PrincipalInfo::TSystemPrincipalInfo) {
     NS_WARNING("Simpledb not allowed for this principal!");
     return NS_ERROR_INVALID_ARG;
   }
 
+  if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(*principalInfo))) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   mPrincipalInfo = std::move(principalInfo);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 SDBConnection::Open(const nsAString& aName, nsISDBRequest** _retval) {
   AssertIsOnOwningThread();
--- a/dom/storage/StorageUtils.h
+++ b/dom/storage/StorageUtils.h
@@ -2,16 +2,18 @@
 /* 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_StorageUtils_h
 #define mozilla_dom_StorageUtils_h
 
+#include "nsStringFwd.h"
+
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 namespace StorageUtils {
 
 nsresult GenerateOriginKey(nsIPrincipal* aPrincipal,
                            nsACString& aOriginAttrSuffix,
--- a/dom/tests/mochitest/localstorage/test_localStorageFromChrome.xhtml
+++ b/dom/tests/mochitest/localstorage/test_localStorageFromChrome.xhtml
@@ -2,18 +2,20 @@
 <head>
 <title>localStorage basic test</title>
 
 <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
 <script type="text/javascript">
 
-function startTest()
+async function startTest()
 {
+  await SpecialPowers.pushPrefEnv({"set": [["dom.storage.client_validation", false]]});
+
   var url = "http://example.com/tests/dom/tests/mochitest/localstorage/frameChromeSlave.html";
   var ios = Components.classes["@mozilla.org/network/io-service;1"]
     .getService(Components.interfaces.nsIIOService);
   var ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
     .getService(Components.interfaces.nsIScriptSecurityManager);
   var dsm = Components.classes["@mozilla.org/dom/localStorage-manager;1"]
     .getService(Components.interfaces.nsIDOMStorageManager);
 
--- a/editor/composer/nsEditingSession.cpp
+++ b/editor/composer/nsEditingSession.cpp
@@ -121,17 +121,17 @@ nsEditingSession::MakeWindowEditable(moz
   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
 
   mDocShell = do_GetWeakReference(docShell);
   mInteractive = aInteractive;
   mMakeWholeDocumentEditable = aMakeWholeDocumentEditable;
 
   nsresult rv;
   if (!mInteractive) {
-    rv = DisableJSAndPlugins(aWindow);
+    rv = DisableJSAndPlugins(*docShell);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Always remove existing editor
   TearDownEditorOnWindow(aWindow);
 
   // Tells embedder that startup is in progress
   mEditorStatus = eEditorCreationInProgress;
@@ -173,68 +173,58 @@ nsEditingSession::MakeWindowEditable(moz
     //  it IS ok to destroy current editor
     if (NS_FAILED(rv)) {
       TearDownEditorOnWindow(aWindow);
     }
   }
   return rv;
 }
 
-NS_IMETHODIMP
-nsEditingSession::DisableJSAndPlugins(mozIDOMWindowProxy* aWindow) {
-  NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
-  nsIDocShell* docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
-  NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
-
+nsresult nsEditingSession::DisableJSAndPlugins(nsIDocShell& aDocShell) {
   bool tmp;
-  nsresult rv = docShell->GetAllowJavascript(&tmp);
+  nsresult rv = aDocShell.GetAllowJavascript(&tmp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mScriptsEnabled = tmp;
 
-  rv = docShell->SetAllowJavascript(false);
+  rv = aDocShell.SetAllowJavascript(false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Disable plugins in this document:
-  mPluginsEnabled = docShell->PluginsAllowedInCurrentDoc();
+  mPluginsEnabled = aDocShell.PluginsAllowedInCurrentDoc();
 
-  rv = docShell->SetAllowPlugins(false);
+  rv = aDocShell.SetAllowPlugins(false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mDisabledJSAndPlugins = true;
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsEditingSession::RestoreJSAndPlugins(mozIDOMWindowProxy* aWindow) {
+nsresult nsEditingSession::RestoreJSAndPlugins(nsPIDOMWindowOuter* aWindow) {
   if (!mDisabledJSAndPlugins) {
     return NS_OK;
   }
 
   mDisabledJSAndPlugins = false;
 
-  NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
-  nsIDocShell* docShell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell();
+  if (NS_WARN_IF(!aWindow)) {
+    // DetachFromWindow may call this method with nullptr.
+    return NS_ERROR_FAILURE;
+  }
+  nsIDocShell* docShell = aWindow->GetDocShell();
   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
 
   nsresult rv = docShell->SetAllowJavascript(mScriptsEnabled);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Disable plugins in this document:
   return docShell->SetAllowPlugins(mPluginsEnabled);
 }
 
-NS_IMETHODIMP
-nsEditingSession::GetJsAndPluginsDisabled(bool* aResult) {
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = mDisabledJSAndPlugins;
-  return NS_OK;
-}
-
 /*---------------------------------------------------------------------------
 
   WindowIsEditable
 
   boolean windowIsEditable (in nsIDOMWindow aWindow);
 ----------------------------------------------------------------------------*/
 NS_IMETHODIMP
 nsEditingSession::WindowIsEditable(mozIDOMWindowProxy* aWindow,
@@ -536,17 +526,17 @@ nsEditingSession::TearDownEditorOnWindow
   // Null out the editor on the docShell to trigger PreDestroy which
   // needs to happen before document state listeners are removed below.
   docShell->SetEditor(nullptr);
 
   RemoveListenersAndControllers(window, htmlEditor);
 
   if (stopEditing) {
     // Make things the way they were before we started editing.
-    RestoreJSAndPlugins(aWindow);
+    RestoreJSAndPlugins(window);
     RestoreAnimationMode(window);
 
     if (mMakeWholeDocumentEditable) {
       doc->SetEditableFlag(false);
       nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(doc);
       if (htmlDocument) {
         htmlDocument->SetEditingState(nsIHTMLDocument::eOff);
       }
@@ -1234,17 +1224,17 @@ nsresult nsEditingSession::DetachFromWin
   }
 
   auto* window = nsPIDOMWindowOuter::From(aWindow);
 
   // Remove controllers, webprogress listener, and otherwise
   // make things the way they were before we started editing.
   RemoveEditorControllers(window);
   RemoveWebProgressListener(window);
-  RestoreJSAndPlugins(aWindow);
+  RestoreJSAndPlugins(window);
   RestoreAnimationMode(window);
 
   // Kill our weak reference to our original window, in case
   // it changes on restore, or otherwise dies.
   mDocShell = nullptr;
 
   return NS_OK;
 }
@@ -1262,17 +1252,17 @@ nsresult nsEditingSession::ReattachToWin
 
   auto* window = nsPIDOMWindowOuter::From(aWindow);
   nsIDocShell* docShell = window->GetDocShell();
   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
   mDocShell = do_GetWeakReference(docShell);
 
   // Disable plugins.
   if (!mInteractive) {
-    rv = DisableJSAndPlugins(aWindow);
+    rv = DisableJSAndPlugins(*docShell);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Tells embedder that startup is in progress.
   mEditorStatus = eEditorCreationInProgress;
 
   // Adds back web progress listener.
   rv = PrepareForEditing(window);
--- a/editor/composer/nsEditingSession.h
+++ b/editor/composer/nsEditingSession.h
@@ -81,16 +81,27 @@ class nsEditingSession final : public ns
   bool IsProgressForTargetDocument(nsIWebProgress* aWebProgress);
 
   void RemoveEditorControllers(nsPIDOMWindowOuter* aWindow);
   void RemoveWebProgressListener(nsPIDOMWindowOuter* aWindow);
   void RestoreAnimationMode(nsPIDOMWindowOuter* aWindow);
   void RemoveListenersAndControllers(nsPIDOMWindowOuter* aWindow,
                                      mozilla::HTMLEditor* aHTMLEditor);
 
+  /**
+   * Disable scripts and plugins in aDocShell.
+   */
+  nsresult DisableJSAndPlugins(nsIDocShell& aDocShell);
+
+  /**
+   * Restore JS and plugins (enable/disable them) according to the state they
+   * were before the last call to disableJSAndPlugins.
+   */
+  nsresult RestoreJSAndPlugins(nsPIDOMWindowOuter* aWindow);
+
  protected:
   bool mDoneSetup;  // have we prepared for editing yet?
 
   // Used to prevent double creation of editor because nsIWebProgressListener
   //  receives a STATE_STOP notification before the STATE_START
   //  for our document, so we wait for the STATE_START, then STATE_STOP
   //  before creating an editor
   bool mCanCreateEditor;
--- a/editor/composer/nsIEditingSession.idl
+++ b/editor/composer/nsIEditingSession.idl
@@ -76,43 +76,27 @@ interface nsIEditingSession : nsISupport
    *   Destroy editor and related support objects
    */
   void tearDownEditorOnWindow(in mozIDOMWindowProxy window);
 
   void setEditorOnControllers(in mozIDOMWindowProxy aWindow,
                               in nsIEditor aEditor);
 
   /**
-   * Disable scripts and plugins in aWindow.
-   */
-  void disableJSAndPlugins(in mozIDOMWindowProxy aWindow);
-
-  /**
-   * Restore JS and plugins (enable/disable them) according to the state they
-   * were before the last call to disableJSAndPlugins.
-   */
-  void restoreJSAndPlugins(in mozIDOMWindowProxy aWindow);
-
-  /**
    * Removes all the editor's controllers/listeners etc and makes the window
    * uneditable.
    */
   void detachFromWindow(in mozIDOMWindowProxy aWindow);
 
   /**
    * Undos detachFromWindow(), reattaches this editing session/editor
    * to the window.
    */
   void reattachToWindow(in mozIDOMWindowProxy aWindow);
 
-  /**
-   * Whether this session has disabled JS and plugins.
-   */
-  readonly attribute boolean jsAndPluginsDisabled;
-
 %{C++
   /**
    * This method is implemented with nsIDocShell::GetHTMLEditor().  I.e.,
    * This method doesn't depend on nsEditingSession.  Therefore, even if
    * there were some implementation of nsIEditingSession interface, this
    * would be safe to use.
    */
   mozilla::HTMLEditor* GetHTMLEditorForWindow(mozIDOMWindowProxy* aWindow);
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -362,17 +362,17 @@ bool BackgroundParentImpl::DeallocPBackg
 }
 
 BackgroundParentImpl::PBackgroundLSSimpleRequestParent*
 BackgroundParentImpl::AllocPBackgroundLSSimpleRequestParent(
     const LSSimpleRequestParams& aParams) {
   AssertIsInMainOrSocketProcess();
   AssertIsOnBackgroundThread();
 
-  return mozilla::dom::AllocPBackgroundLSSimpleRequestParent(aParams);
+  return mozilla::dom::AllocPBackgroundLSSimpleRequestParent(this, aParams);
 }
 
 mozilla::ipc::IPCResult
 BackgroundParentImpl::RecvPBackgroundLSSimpleRequestConstructor(
     PBackgroundLSSimpleRequestParent* aActor,
     const LSSimpleRequestParams& aParams) {
   AssertIsInMainOrSocketProcess();
   AssertIsOnBackgroundThread();
@@ -979,16 +979,30 @@ BackgroundParentImpl::PQuotaParent* Back
 bool BackgroundParentImpl::DeallocPQuotaParent(PQuotaParent* aActor) {
   AssertIsInMainOrSocketProcess();
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   return mozilla::dom::quota::DeallocPQuotaParent(aActor);
 }
 
+mozilla::ipc::IPCResult BackgroundParentImpl::RecvShutdownQuotaManager() {
+  AssertIsInMainOrSocketProcess();
+  AssertIsOnBackgroundThread();
+
+  if (BackgroundParent::IsOtherProcessActor(this)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  if (!mozilla::dom::quota::RecvShutdownQuotaManager()) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
 dom::PFileSystemRequestParent*
 BackgroundParentImpl::AllocPFileSystemRequestParent(
     const FileSystemParams& aParams) {
   AssertIsInMainOrSocketProcess();
   AssertIsOnBackgroundThread();
 
   RefPtr<FileSystemRequestParent> result = new FileSystemRequestParent();
 
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -270,16 +270,18 @@ class BackgroundParentImpl : public PBac
 
   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;
 
   virtual mozilla::ipc::IPCResult RecvPFileSystemRequestConstructor(
       PFileSystemRequestParent* actor, const FileSystemParams& params) override;
 
   virtual bool DeallocPFileSystemRequestParent(
       PFileSystemRequestParent*) override;
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -125,16 +125,25 @@ already_AddRefed<nsIPrincipal> Principal
         if (NS_WARN_IF(NS_FAILED(rv))) {
           return nullptr;
         }
         static_cast<nsCSPContext*>(csp.get())->SetIPCPolicies(
             info.securityPolicies());
         principal->SetCsp(csp);
       }
 
+      if (!info.baseDomain().IsVoid()) {
+        nsAutoCString baseDomain;
+        rv = principal->GetBaseDomain(baseDomain);
+        if (NS_WARN_IF(NS_FAILED(rv)) ||
+            !info.baseDomain().Equals(baseDomain)) {
+          MOZ_CRASH("Base domain must be available when deserialized");
+        }
+      }
+
       return principal.forget();
     }
 
     case PrincipalInfo::TExpandedPrincipalInfo: {
       const ExpandedPrincipalInfo& info =
           aPrincipalInfo.get_ExpandedPrincipalInfo();
 
       nsTArray<nsCOMPtr<nsIPrincipal>> allowlist;
@@ -307,19 +316,26 @@ nsresult PrincipalToPrincipalInfo(nsIPri
     return rv;
   }
 
   nsTArray<ContentSecurityPolicy> policies;
   if (csp) {
     PopulateContentSecurityPolicies(csp, policies);
   }
 
+  // This attribute is not crucial.
+  nsCString baseDomain;
+  if (NS_FAILED(aPrincipal->GetBaseDomain(baseDomain))) {
+    NS_WARNING("Failed to get base domain!");
+    baseDomain.SetIsVoid(true);
+  }
+
   *aPrincipalInfo =
       ContentPrincipalInfo(aPrincipal->OriginAttributesRef(), originNoSuffix,
-                           spec, domain, std::move(policies));
+                           spec, domain, std::move(policies), baseDomain);
   return NS_OK;
 }
 
 bool IsPincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo) {
   if (aPrincipalInfo.type() != ipc::PrincipalInfo::TContentPrincipalInfo) {
     return false;
   }
 
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -186,16 +186,18 @@ parent:
   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();
 
   async PGamepadTestChannel();
 
   async PHttpBackgroundChannel(uint64_t channelId);
 
--- a/ipc/glue/PBackgroundSharedTypes.ipdlh
+++ b/ipc/glue/PBackgroundSharedTypes.ipdlh
@@ -28,16 +28,19 @@ struct ContentPrincipalInfo
   // here allows us to retrive the origin without creating a full nsIPrincipal.
   nsCString originNoSuffix;
 
   nsCString spec;
 
   nsCString? domain;
 
   ContentSecurityPolicy[] securityPolicies;
+
+  // Like originNoSuffix, baseDomain is used out of the main-thread.
+  nsCString baseDomain;
 };
 
 struct SystemPrincipalInfo
 { };
 
 struct NullPrincipalInfo
 {
   OriginAttributes attrs;
--- a/js/public/ContextOptions.h
+++ b/js/public/ContextOptions.h
@@ -23,17 +23,17 @@ class JS_PUBLIC_API ContextOptions {
         asmJS_(true),
         wasm_(true),
         wasmVerbose_(false),
         wasmBaseline_(true),
         wasmIon_(true),
 #ifdef ENABLE_WASM_CRANELIFT
         wasmCranelift_(false),
 #endif
-#ifdef ENABLE_WASM_REFTYPES
+#ifdef ENABLE_WASM_GC
         wasmGc_(false),
 #endif
         testWasmAwaitTier2_(false),
         throwOnAsmJSValidationFailure_(false),
         nativeRegExp_(true),
         asyncStack_(true),
         throwOnDebuggeeWouldRun_(true),
         dumpStackOnDebuggeeWouldRun_(false),
@@ -114,17 +114,17 @@ class JS_PUBLIC_API ContextOptions {
 #endif
 
   bool testWasmAwaitTier2() const { return testWasmAwaitTier2_; }
   ContextOptions& setTestWasmAwaitTier2(bool flag) {
     testWasmAwaitTier2_ = flag;
     return *this;
   }
 
-#ifdef ENABLE_WASM_REFTYPES
+#ifdef ENABLE_WASM_GC
   bool wasmGc() const { return wasmGc_; }
   ContextOptions& setWasmGc(bool flag) {
     wasmGc_ = flag;
     return *this;
   }
 #endif
 
   bool throwOnAsmJSValidationFailure() const {
@@ -205,34 +205,34 @@ class JS_PUBLIC_API ContextOptions {
 
   void disableOptionsForSafeMode() {
     setBaseline(false);
     setIon(false);
     setAsmJS(false);
     setWasm(false);
     setWasmBaseline(false);
     setWasmIon(false);
-#ifdef ENABLE_WASM_REFTYPES
+#ifdef ENABLE_WASM_GC
     setWasmGc(false);
 #endif
     setNativeRegExp(false);
   }
 
  private:
   bool baseline_ : 1;
   bool ion_ : 1;
   bool asmJS_ : 1;
   bool wasm_ : 1;
   bool wasmVerbose_ : 1;
   bool wasmBaseline_ : 1;
   bool wasmIon_ : 1;
 #ifdef ENABLE_WASM_CRANELIFT
   bool wasmCranelift_ : 1;
 #endif
-#ifdef ENABLE_WASM_REFTYPES
+#ifdef ENABLE_WASM_GC
   bool wasmGc_ : 1;
 #endif
   bool testWasmAwaitTier2_ : 1;
   bool throwOnAsmJSValidationFailure_ : 1;
   bool nativeRegExp_ : 1;
   bool asyncStack_ : 1;
   bool throwOnDebuggeeWouldRun_ : 1;
   bool dumpStackOnDebuggeeWouldRun_ : 1;
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -4059,17 +4059,17 @@ static MOZ_ALWAYS_INLINE ArrayObject* Ne
                                                     gc::AllocKind::OBJECT0));
   if (!shape) {
     return nullptr;
   }
 
   AutoSetNewObjectMetadata metadata(cx);
   RootedArrayObject arr(
       cx, ArrayObject::createArray(
-              cx, allocKind, GetInitialHeap(newKind, &ArrayObject::class_),
+              cx, allocKind, GetInitialHeap(newKind, group),
               shape, group, length, metadata));
   if (!arr) {
     return nullptr;
   }
 
   if (shape->isEmptyShape()) {
     if (!AddLengthProperty(cx, arr)) {
       return nullptr;
@@ -4148,17 +4148,17 @@ ArrayObject* js::NewDenseFullyAllocatedA
   AutoSetNewObjectMetadata metadata(cx);
   gc::AllocKind allocKind = GuessArrayGCKind(length);
   MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_));
   allocKind = GetBackgroundAllocKind(allocKind);
 
   RootedObjectGroup group(cx, templateObject->group());
   RootedShape shape(cx, templateObject->as<ArrayObject>().lastProperty());
 
-  gc::InitialHeap heap = GetInitialHeap(GenericObject, &ArrayObject::class_);
+  gc::InitialHeap heap = GetInitialHeap(GenericObject, group);
   Rooted<ArrayObject*> arr(
       cx, ArrayObject::createArray(cx, allocKind, heap, shape, group, length,
                                    metadata));
   if (!arr) {
     return nullptr;
   }
 
   if (!EnsureNewArrayElements(cx, arr, length)) {
@@ -4166,20 +4166,21 @@ ArrayObject* js::NewDenseFullyAllocatedA
   }
 
   probes::CreateObject(cx, arr);
 
   return arr;
 }
 
 ArrayObject* js::NewDenseCopyOnWriteArray(JSContext* cx,
-                                          HandleArrayObject templateObject,
-                                          gc::InitialHeap heap) {
+                                          HandleArrayObject templateObject) {
   MOZ_ASSERT(!gc::IsInsideNursery(templateObject));
 
+  gc::InitialHeap heap = GetInitialHeap(GenericObject, templateObject->group());
+
   ArrayObject* arr =
       ArrayObject::createCopyOnWriteArray(cx, heap, templateObject);
   if (!arr) {
     return nullptr;
   }
 
   probes::CreateObject(cx, arr);
   return arr;
--- a/js/src/builtin/Array.h
+++ b/js/src/builtin/Array.h
@@ -74,18 +74,17 @@ extern ArrayObject* NewDenseCopiedArray(
                                         NewObjectKind newKind = GenericObject);
 
 // Create a dense array based on templateObject with the given length.
 extern ArrayObject* NewDenseFullyAllocatedArrayWithTemplate(
     JSContext* cx, uint32_t length, JSObject* templateObject);
 
 // Create a dense array with the same copy-on-write elements as another object.
 extern ArrayObject* NewDenseCopyOnWriteArray(JSContext* cx,
-                                             HandleArrayObject templateObject,
-                                             gc::InitialHeap heap);
+                                             HandleArrayObject templateObject);
 
 extern ArrayObject* NewFullyAllocatedArrayTryUseGroup(
     JSContext* cx, HandleObjectGroup group, size_t length,
     NewObjectKind newKind = GenericObject);
 
 extern ArrayObject* NewPartlyAllocatedArrayTryUseGroup(JSContext* cx,
                                                        HandleObjectGroup group,
                                                        size_t length);
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -1548,17 +1548,17 @@ static MOZ_MUST_USE JSObject* ReadableSt
   }
 
   // Step 3: Assert: Type(done) is Boolean (implicit).
 
   // Step 4: Let obj be ObjectCreate(prototype).
   NativeObject* obj;
   JS_TRY_VAR_OR_RETURN_NULL(
       cx, obj,
-      NativeObject::createWithTemplate(cx, gc::DefaultHeap, templateObject));
+      NativeObject::createWithTemplate(cx, templateObject));
 
   // Step 5: Perform CreateDataProperty(obj, "value", value).
   obj->setSlot(Realm::IterResultObjectValueSlot, value);
 
   // Step 6: Perform CreateDataProperty(obj, "done", done).
   obj->setSlot(Realm::IterResultObjectDoneSlot,
                done ? TrueHandleValue : FalseHandleValue);
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -696,21 +696,17 @@ static bool WasmBulkMemSupported(JSConte
 static bool WasmReftypesEnabled(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   args.rval().setBoolean(wasm::HasReftypesSupport(cx));
   return true;
 }
 
 static bool WasmGcEnabled(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
-#ifdef ENABLE_WASM_GC
-  args.rval().setBoolean(wasm::HasReftypesSupport(cx));
-#else
-  args.rval().setBoolean(false);
-#endif
+  args.rval().setBoolean(wasm::HasGcSupport(cx));
   return true;
 }
 
 static bool WasmDebugSupport(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   args.rval().setBoolean(cx->options().wasmBaseline() &&
                          wasm::BaselineCanCompile());
   return true;
@@ -6026,25 +6022,25 @@ gc::ZealModeHelpText),
 "  until background compilation is complete."),
 
     JS_FN_HELP("wasmHasTier2CompilationCompleted", WasmHasTier2CompilationCompleted, 1, 0,
 "wasmHasTier2CompilationCompleted(module)",
 "  Returns a boolean indicating whether a given module has finished compiled code for tier2. \n"
 "This will return true early if compilation isn't two-tiered. "),
 
     JS_FN_HELP("wasmReftypesEnabled", WasmReftypesEnabled, 1, 0,
-"wasmReftypesEnabled(bool)",
+"wasmReftypesEnabled()",
 "  Returns a boolean indicating whether the WebAssembly reftypes proposal is enabled."),
 
     JS_FN_HELP("wasmGcEnabled", WasmGcEnabled, 1, 0,
-"wasmGcEnabled(bool)",
+"wasmGcEnabled()",
 "  Returns a boolean indicating whether the WebAssembly GC types proposal is enabled."),
 
     JS_FN_HELP("wasmDebugSupport", WasmDebugSupport, 1, 0,
-"wasmDebugSupport(bool)",
+"wasmDebugSupport()",
 "  Returns a boolean indicating whether the WebAssembly compilers support debugging."),
 
     JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
 "isLazyFunction(fun)",
 "  True if fun is a lazy JSFunction."),
 
     JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
 "isRelazifiableFunction(fun)",
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2312,17 +2312,17 @@ bool TypedObject::construct(JSContext* c
     JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
     js::HandleShape shape, js::HandleObjectGroup group) {
   debugCheckNewObject(group, shape, kind, heap);
 
   const js::Class* clasp = group->clasp();
   MOZ_ASSERT(::IsTypedObjectClass(clasp));
 
   JSObject* obj =
-      js::Allocate<JSObject>(cx, kind, /* nDynamicSlots = */ 0, heap, clasp);
+      js::AllocateObject(cx, kind, /* nDynamicSlots = */ 0, heap, clasp);
   if (!obj) {
     return cx->alreadyReportedOOM();
   }
 
   TypedObject* tobj = static_cast<TypedObject*>(obj);
   tobj->initGroup(group);
   tobj->initShape(shape);
 
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -20,21 +20,19 @@
 #include "gc/ArenaList-inl.h"
 #include "gc/Heap-inl.h"
 #include "gc/PrivateIterators-inl.h"
 #include "vm/JSObject-inl.h"
 
 using namespace js;
 using namespace gc;
 
-template <typename T, AllowGC allowGC /* = CanGC */>
-JSObject* js::Allocate(JSContext* cx, AllocKind kind, size_t nDynamicSlots,
-                       InitialHeap heap, const Class* clasp) {
-  static_assert(mozilla::IsConvertible<T*, JSObject*>::value,
-                "must be JSObject derived");
+template <AllowGC allowGC /* = CanGC */>
+JSObject* js::AllocateObject(JSContext* cx, AllocKind kind, size_t nDynamicSlots,
+                             InitialHeap heap, const Class* clasp) {
   MOZ_ASSERT(IsObjectAllocKind(kind));
   size_t thingSize = Arena::thingSize(kind);
 
   MOZ_ASSERT(thingSize == Arena::thingSize(kind));
   MOZ_ASSERT(thingSize >= sizeof(JSObject_Slots0));
   static_assert(
       sizeof(JSObject_Slots0) >= MinCellSize,
       "All allocations must be at least the allocator-imposed minimum size.");
@@ -72,26 +70,26 @@ JSObject* js::Allocate(JSContext* cx, Al
     if (!allowGC) {
       return nullptr;
     }
   }
 
   return GCRuntime::tryNewTenuredObject<allowGC>(cx, kind, thingSize,
                                                  nDynamicSlots);
 }
-template JSObject* js::Allocate<JSObject, NoGC>(JSContext* cx,
-                                                gc::AllocKind kind,
-                                                size_t nDynamicSlots,
-                                                gc::InitialHeap heap,
-                                                const Class* clasp);
-template JSObject* js::Allocate<JSObject, CanGC>(JSContext* cx,
-                                                 gc::AllocKind kind,
-                                                 size_t nDynamicSlots,
-                                                 gc::InitialHeap heap,
-                                                 const Class* clasp);
+template JSObject* js::AllocateObject<NoGC>(JSContext* cx,
+                                            gc::AllocKind kind,
+                                            size_t nDynamicSlots,
+                                            gc::InitialHeap heap,
+                                            const Class* clasp);
+template JSObject* js::AllocateObject<CanGC>(JSContext* cx,
+                                               gc::AllocKind kind,
+                                               size_t nDynamicSlots,
+                                               gc::InitialHeap heap,
+                                               const Class* clasp);
 
 // Attempt to allocate a new JSObject out of the nursery. If there is not
 // enough room in the nursery or there is an OOM, this method will return
 // nullptr.
 template <AllowGC allowGC>
 JSObject* GCRuntime::tryNewNurseryObject(JSContext* cx, size_t thingSize,
                                          size_t nDynamicSlots,
                                          const Class* clasp) {
@@ -172,17 +170,17 @@ JSString* GCRuntime::tryNewNurseryString
       return static_cast<JSString*>(
           cx->nursery().allocateString(cx->zone(), thingSize, kind));
     }
   }
   return nullptr;
 }
 
 template <typename StringAllocT, AllowGC allowGC /* = CanGC */>
-StringAllocT* js::AllocateString(JSContext* cx, InitialHeap heap) {
+StringAllocT* js::AllocateStringImpl(JSContext* cx, InitialHeap heap) {
   static_assert(mozilla::IsConvertible<StringAllocT*, JSString*>::value,
                 "must be JSString derived");
 
   AllocKind kind = MapTypeToFinalizeKind<StringAllocT>::kind;
   size_t size = sizeof(StringAllocT);
   MOZ_ASSERT(size == Arena::thingSize(kind));
   MOZ_ASSERT(size == sizeof(JSString) || size == sizeof(JSFatInlineString));
 
@@ -219,20 +217,20 @@ StringAllocT* js::AllocateString(JSConte
     }
   }
 
   return GCRuntime::tryNewTenuredThing<StringAllocT, allowGC>(cx, kind, size);
 }
 
 #define DECL_ALLOCATOR_INSTANCES(allocKind, traceKind, type, sizedType, \
                                  bgfinal, nursery, compact)             \
-  template type* js::AllocateString<type, NoGC>(JSContext * cx,         \
-                                                InitialHeap heap);      \
-  template type* js::AllocateString<type, CanGC>(JSContext * cx,        \
-                                                 InitialHeap heap);
+  template type* js::AllocateStringImpl<type, NoGC>(JSContext * cx,     \
+                                                    InitialHeap heap);  \
+  template type* js::AllocateStringImpl<type, CanGC>(JSContext * cx,    \
+                                                     InitialHeap heap);
 FOR_EACH_NURSERY_STRING_ALLOCKIND(DECL_ALLOCATOR_INSTANCES)
 #undef DECL_ALLOCATOR_INSTANCES
 
 template <typename T, AllowGC allowGC /* = CanGC */>
 T* js::Allocate(JSContext* cx) {
   static_assert(!mozilla::IsConvertible<T*, JSObject*>::value,
                 "must not be JSObject derived");
   static_assert(
--- a/js/src/gc/Allocator.h
+++ b/js/src/gc/Allocator.h
@@ -12,54 +12,59 @@
 #include "js/RootingAPI.h"
 
 class JSFatInlineString;
 
 namespace js {
 
 struct Class;
 
-// Allocate a new GC thing. After a successful allocation the caller must
-// fully initialize the thing before calling any function that can potentially
-// trigger GC. This will ensure that GC tracing never sees junk values stored
-// in the partially initialized thing.
-
+// Allocate a new GC thing that's not a JSObject or a string.
+//
+// After a successful allocation the caller must fully initialize the thing
+// before calling any function that can potentially trigger GC. This will ensure
+// that GC tracing never sees junk values stored in the partially initialized
+// thing.
 template <typename T, AllowGC allowGC = CanGC>
 T* Allocate(JSContext* cx);
 
-// Use for JSObject. A longer signature that includes additional information in
-// support of various optimizations. If dynamic slots are requested they will be
-// allocated and the pointer stored directly in |NativeObject::slots_|.
-template <typename, AllowGC allowGC = CanGC>
-JSObject* Allocate(JSContext* cx, gc::AllocKind kind, size_t nDynamicSlots,
-                   gc::InitialHeap heap, const Class* clasp);
+// Allocate a JSObject.
+//
+// A longer signature that includes additional information in support of various
+// optimizations. If dynamic slots are requested they will be allocated and the
+// pointer stored directly in |NativeObject::slots_|.
+template <AllowGC allowGC = CanGC>
+JSObject* AllocateObject(JSContext* cx, gc::AllocKind kind, size_t nDynamicSlots,
+                         gc::InitialHeap heap, const Class* clasp);
 
 // Internal function used for nursery-allocatable strings.
 template <typename StringAllocT, AllowGC allowGC = CanGC>
-StringAllocT* AllocateString(JSContext* cx, gc::InitialHeap heap);
+StringAllocT* AllocateStringImpl(JSContext* cx, gc::InitialHeap heap);
 
+// Allocate a string.
+//
 // Use for nursery-allocatable strings. Returns a value cast to the correct
 // type.
 template <typename StringT, AllowGC allowGC = CanGC>
-StringT* Allocate(JSContext* cx, gc::InitialHeap heap) {
-  return static_cast<StringT*>(js::AllocateString<JSString, allowGC>(cx, heap));
+StringT* AllocateString(JSContext* cx, gc::InitialHeap heap) {
+  return static_cast<StringT*>(AllocateStringImpl<JSString, allowGC>(cx, heap));
 }
 
 // Specialization for JSFatInlineString that must use a different allocation
 // type. Note that we have to explicitly specialize for both values of AllowGC
 // because partial function specialization is not allowed.
 template <>
-inline JSFatInlineString* Allocate<JSFatInlineString, CanGC>(
+inline JSFatInlineString* AllocateString<JSFatInlineString, CanGC>(
     JSContext* cx, gc::InitialHeap heap) {
   return static_cast<JSFatInlineString*>(
-      js::AllocateString<JSFatInlineString, CanGC>(cx, heap));
+      js::AllocateStringImpl<JSFatInlineString, CanGC>(cx, heap));
 }
 
 template <>
-inline JSFatInlineString* Allocate<JSFatInlineString, NoGC>(
+inline JSFatInlineString* AllocateString<JSFatInlineString, NoGC>(
     JSContext* cx, gc::InitialHeap heap) {
   return static_cast<JSFatInlineString*>(
-      js::AllocateString<JSFatInlineString, NoGC>(cx, heap));
+      js::AllocateStringImpl<JSFatInlineString, NoGC>(cx, heap));
 }
 
 }  // namespace js
 
 #endif  // gc_Allocator_h
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -104,16 +104,64 @@ const RefFuncCode      = 0xd2;
 
 const FirstInvalidOpcode = 0xc5;
 const LastInvalidOpcode = 0xfb;
 const MiscPrefix = 0xfc;
 const SimdPrefix = 0xfd;
 const ThreadPrefix = 0xfe;
 const MozPrefix = 0xff;
 
+// See WasmConstants.h for documentation.
+// Limit this to a group of 8 per line.
+
+const definedOpcodes =
+    [0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+     0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+     0x10, 0x11,
+     0x1a, 0x1b,
+     0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
+     0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+     0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+     0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+     0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+     0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+     0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+     0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+     0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+     0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+     0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+     0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+     0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+     0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+     0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+     0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+     0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+     0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+     0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+     0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+     0xc0, 0xc1, 0xc2, 0xc3, 0xc4,
+     0xd0, 0xd1, 0xd2,
+     0xf0,
+     0xfc, 0xfe, 0xff ];
+
+const undefinedOpcodes = (function () {
+    let a = [];
+    let j = 0;
+    let i = 0;
+    while (i < 256) {
+        while (definedOpcodes[j] > i)
+            a.push(i++);
+        assertEq(definedOpcodes[j], i);
+        i++;
+        j++;
+    }
+    assertEq(definedOpcodes.length + a.length, 256);
+    return a;
+})();
+
 // Secondary opcode bytes for misc prefix
 const MemoryInitCode = 0x08;    // Pending
 const DataDropCode = 0x09;      // Pending
 const MemoryCopyCode = 0x0a;    // Pending
 const MemoryFillCode = 0x0b;    // Pending
 const TableInitCode = 0x0c;     // Pending
 const ElemDropCode = 0x0d;      // Pending
 const TableCopyCode = 0x0e;     // Pending
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -230,18 +230,18 @@ assertErrorMessage(() => wasmEval(module
     nameSection([moduleNameSubsection('hi')])])
 ).f(), RuntimeError, /unreachable/);
 
 // Diagnose nonstandard block signature types.
 for (var bad of [0xff, 0, 1, 0x3f])
     assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid inline block type/);
 
 // Ensure all invalid opcodes rejected
-for (let i = FirstInvalidOpcode; i <= LastInvalidOpcode; i++) {
-    let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[i]})])]);
+for (let op of undefinedOpcodes) {
+    let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[op]})])]);
     assertErrorMessage(() => wasmEval(binary), CompileError, /unrecognized opcode/);
     assertEq(WebAssembly.validate(binary), false);
 }
 
 // Prefixed opcodes
 
 function checkIllegalPrefixed(prefix, opcode) {
     let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[prefix, opcode]})])]);
--- a/js/src/jit-test/tests/wasm/gc/anyref-boxing.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-boxing.js
@@ -53,32 +53,30 @@ for (let v of VALUES)
 
 // Set via initialization, then read via get_global and returned
 
 for (let v of VALUES)
 {
     let g = new WebAssembly.Global({value: "anyref"}, v);
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (import $glob "m" "g" (global anyref))
            (func (export "f") (result anyref)
              (get_global $glob)))`,
         {m:{g}});
     assertEq(ins.exports.f(), v);
 }
 
 // Set via set_global, then read via 'value'
 
 for (let v of VALUES)
 {
     let g = new WebAssembly.Global({value: "anyref", mutable: true});
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (import $glob "m" "g" (global (mut anyref)))
            (func (export "f") (param $v anyref)
              (set_global $glob (get_local $v))))`,
         {m:{g}});
     ins.exports.f(v);
     assertEq(g.value, v);
 }
 
@@ -107,33 +105,31 @@ for (let v of VALUES)
 
 // write with table.set, read with .get()
 
 for (let v of VALUES)
 {
     let t = new WebAssembly.Table({element: "anyref", initial: 10});
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (import $t "m" "t" (table 10 anyref))
            (func (export "f") (param $v anyref)
              (table.set $t (i32.const 3) (get_local $v))))`,
         {m:{t}});
     ins.exports.f(v);
     assertEq(t.get(3), v);
 }
 
 // write with .set(), read with table.get
 
 for (let v of VALUES)
 {
     let t = new WebAssembly.Table({element: "anyref", initial: 10});
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (import $t "m" "t" (table 10 anyref))
            (func (export "f") (result anyref)
              (table.get $t (i32.const 3))))`,
         {m:{t}});
     t.set(3, v);
     assertEq(ins.exports.f(), v);
 }
 
@@ -141,17 +137,16 @@ for (let v of VALUES)
 // them.
 
 for (let v of VALUES)
 {
     let returner = function () { return v; };
     let receiver = function (w) { assertEq(w, v); };
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (import $returner "m" "returner" (func (result anyref)))
            (import $receiver "m" "receiver" (func (param anyref)))
            (func (export "test_returner") (result anyref)
              (call $returner))
            (func (export "test_receiver") (param $v anyref)
              (call $receiver (get_local $v))))`,
         {m:{returner, receiver}});
     assertEq(ins.exports.test_returner(), v);
--- a/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-postbarrier.js
@@ -5,37 +5,34 @@ const { startProfiling, endProfiling, as
 // Dummy constructor.
 function Baguette(calories) {
     this.calories = calories;
 }
 
 // Ensure the baseline compiler sync's before the postbarrier.
 (function() {
     wasmEvalText(`(module
-        (gc_feature_opt_in 3)
         (global (mut anyref) (ref.null))
         (func (export "f")
             get_global 0
             ref.null
             set_global 0
             set_global 0
         )
     )`).exports.f();
 })();
 
 let exportsPlain = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (global i32 (i32.const 42))
     (global $g (mut anyref) (ref.null))
     (func (export "set") (param anyref) get_local 0 set_global $g)
     (func (export "get") (result anyref) get_global $g)
 )`).exports;
 
 let exportsObj = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (global $g (export "g") (mut anyref) (ref.null))
     (func (export "set") (param anyref) get_local 0 set_global $g)
     (func (export "get") (result anyref) get_global $g)
 )`).exports;
 
 // 7 => Generational GC zeal.
 gczeal(7, 1);
 
--- a/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-global-prebarrier.js
@@ -5,17 +5,16 @@
 
 opts = getJitCompilerOptions();
 if (opts['ion.enable'] || opts['baseline.enable'])
   quit();
 
 const { startProfiling, endProfiling, assertEqPreciseStacks, isSingleStepProfilingEnabled } = WasmHelpers;
 
 let e = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (global $g (mut anyref) (ref.null))
     (func (export "set") (param anyref) get_local 0 set_global $g)
 )`).exports;
 
 let obj = { field: null };
 
 // GCZeal mode 4 implies that prebarriers are being verified at many
 // locations in the interpreter, during interrupt checks, etc. It can be ultra
--- a/js/src/jit-test/tests/wasm/gc/anyref-val-tracing.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref-val-tracing.js
@@ -1,13 +1,12 @@
 // |jit-test| skip-if: !wasmReftypesEnabled()
 
 gczeal(14, 1);
 let { exports } = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (global $anyref (import "glob" "anyref") anyref)
     (func (export "get") (result anyref) get_global $anyref)
 )`, {
     glob: {
         anyref: { sentinel: "lol" },
     }
 });
 assertEq(exports.get().sentinel, "lol");
--- a/js/src/jit-test/tests/wasm/gc/anyref.js
+++ b/js/src/jit-test/tests/wasm/gc/anyref.js
@@ -5,67 +5,63 @@ function Baguette(calories) {
     this.calories = calories;
 }
 
 // Type checking.
 
 const { validate, CompileError } = WebAssembly;
 
 assertErrorMessage(() => wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (func (result anyref)
         i32.const 42
     )
 )`), CompileError, mismatchError('i32', 'anyref'));
 
 assertErrorMessage(() => wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (func (result anyref)
         i32.const 0
         ref.null
         i32.const 42
         select
     )
 )`), CompileError, /select operand types/);
 
 assertErrorMessage(() => wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (func (result i32)
         ref.null
         if
             i32.const 42
         end
     )
 )`), CompileError, mismatchError('nullref', 'i32'));
 
 
 // Basic compilation tests.
 
 let simpleTests = [
-    "(module (gc_feature_opt_in 3) (func (drop (ref.null))))",
-    "(module (gc_feature_opt_in 3) (func $test (local anyref)))",
-    "(module (gc_feature_opt_in 3) (func $test (param anyref)))",
-    "(module (gc_feature_opt_in 3) (func $test (result anyref) (ref.null)))",
-    "(module (gc_feature_opt_in 3) (func $test (block anyref (unreachable)) unreachable))",
-    "(module (gc_feature_opt_in 3) (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
-    `(module (gc_feature_opt_in 3) (import "a" "b" (param anyref)))`,
-    `(module (gc_feature_opt_in 3) (import "a" "b" (result anyref)))`,
-    `(module (gc_feature_opt_in 3) (global anyref (ref.null)))`,
-    `(module (gc_feature_opt_in 3) (global (mut anyref) (ref.null)))`,
+    "(module (func (drop (ref.null))))",
+    "(module (func $test (local anyref)))",
+    "(module (func $test (param anyref)))",
+    "(module (func $test (result anyref) (ref.null)))",
+    "(module (func $test (block anyref (unreachable)) unreachable))",
+    "(module (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
+    `(module (import "a" "b" (param anyref)))`,
+    `(module (import "a" "b" (result anyref)))`,
+    `(module (global anyref (ref.null)))`,
+    `(module (global (mut anyref) (ref.null)))`,
 ];
 
 for (let src of simpleTests) {
     wasmEvalText(src, {a:{b(){}}});
     assertEq(validate(wasmTextToBinary(src)), true);
 }
 
 // Basic behavioral tests.
 
 let { exports } = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (func (export "is_null") (result i32)
         ref.null
         ref.is_null
     )
 
     (func $sum (result i32) (param i32)
         get_local 0
         i32.const 42
@@ -93,17 +89,16 @@ let { exports } = wasmEvalText(`(module
 
 assertEq(exports.is_null(), 1);
 assertEq(exports.is_null_spill(), 1);
 assertEq(exports.is_null_local(), 1);
 
 // Anyref param and result in wasm functions.
 
 exports = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (func (export "is_null") (result i32) (param $ref anyref)
         get_local $ref
         ref.is_null
     )
 
     (func (export "ref_or_null") (result anyref) (param $ref anyref) (param $selector i32)
         get_local $ref
         ref.null
@@ -151,33 +146,31 @@ assertEq(ref.calories, baguette.calories
 
 ref = exports.nested(baguette, 0);
 assertEq(ref, baguette);
 assertEq(ref.calories, baguette.calories);
 
 // Make sure grow-memory isn't blocked by the lack of gc.
 (function() {
     assertEq(wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (memory 0 64)
     (func (export "f") (param anyref) (result i32)
         i32.const 10
         memory.grow
         drop
         memory.size
     )
 )`).exports.f({}), 10);
 })();
 
 // More interesting use cases about control flow joins.
 
 function assertJoin(body) {
     let val = { i: -1 };
     assertEq(wasmEvalText(`(module
-        (gc_feature_opt_in 3)
         (func (export "test") (param $ref anyref) (param $i i32) (result anyref)
             ${body}
         )
     )`).exports.test(val), val);
     assertEq(val.i, -1);
 }
 
 assertJoin("(block anyref get_local $ref)");
@@ -234,17 +227,16 @@ assertJoin(`(block $out anyref (block $u
     i32.add
     tee_local $i
     br_table $unreachable $out
     ) unreachable))
 `);
 
 let x = { i: 42 }, y = { f: 53 };
 exports = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (func (export "test") (param $lhs anyref) (param $rhs anyref) (param $i i32) (result anyref)
         get_local $lhs
         get_local $rhs
         get_local $i
         select
     )
 )`).exports;
 
@@ -289,17 +281,16 @@ let imports = {
         },
         ret() {
             return imports.myBaguette;
         }
     }
 };
 
 exports = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (import $ret "funcs" "ret" (result anyref))
     (import $param "funcs" "param" (param anyref))
 
     (func (export "param") (param $x anyref) (param $y anyref)
         get_local $y
         get_local $x
         call $param
         call $param
@@ -318,17 +309,16 @@ imports.myBaguette = null;
 assertEq(exports.ret(), null);
 
 imports.myBaguette = new Baguette(1337);
 assertEq(exports.ret(), imports.myBaguette);
 
 // Check lazy stubs generation.
 
 exports = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (import $mirror "funcs" "mirror" (param anyref) (result anyref))
     (import $augment "funcs" "augment" (param anyref) (result anyref))
 
     (global $count_f (mut i32) (i32.const 0))
     (global $count_g (mut i32) (i32.const 0))
 
     (func $f (param $param anyref) (result anyref)
         i32.const 1
@@ -402,35 +392,34 @@ assertEq(x.i, 24);
 assertEq(x.newProp, "hello");
 assertEq(exports.count_f(), 1);
 assertEq(exports.count_g(), 1);
 
 // Globals.
 
 // Anyref globals in wasm modules.
 
-assertErrorMessage(() => wasmEvalText(`(module (gc_feature_opt_in 3) (global (import "glob" "anyref") anyref))`, { glob: { anyref: new WebAssembly.Global({ value: 'i32' }, 42) } }),
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "anyref") anyref))`, { glob: { anyref: new WebAssembly.Global({ value: 'i32' }, 42) } }),
     WebAssembly.LinkError,
     /imported global type mismatch/);
 
-assertErrorMessage(() => wasmEvalText(`(module (gc_feature_opt_in 3) (global (import "glob" "i32") i32))`, { glob: { i32: {} } }),
+assertErrorMessage(() => wasmEvalText(`(module (global (import "glob" "i32") i32))`, { glob: { i32: {} } }),
     WebAssembly.LinkError,
     /import object field 'i32' is not a Number/);
 
 imports = {
     constants: {
         imm_null: null,
         imm_bread: new Baguette(321),
         mut_null: new WebAssembly.Global({ value: "anyref", mutable: true }, null),
         mut_bread: new WebAssembly.Global({ value: "anyref", mutable: true }, new Baguette(123))
     }
 };
 
 exports = wasmEvalText(`(module
-    (gc_feature_opt_in 3)
     (global $g_imp_imm_null  (import "constants" "imm_null") anyref)
     (global $g_imp_imm_bread (import "constants" "imm_bread") anyref)
 
     (global $g_imp_mut_null   (import "constants" "mut_null") (mut anyref))
     (global $g_imp_mut_bread  (import "constants" "mut_bread") (mut anyref))
 
     (global $g_imm_null     anyref (ref.null))
     (global $g_imm_getglob  anyref (get_global $g_imp_imm_bread))
--- a/js/src/jit-test/tests/wasm/gc/debugger.js
+++ b/js/src/jit-test/tests/wasm/gc/debugger.js
@@ -1,23 +1,22 @@
 // |jit-test| skip-if: !wasmReftypesEnabled() || !wasmDebuggingIsSupported()
 
 (function() {
     let g = newGlobal({newCompartment: true});
     let dbg = new Debugger(g);
-    g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (gc_feature_opt_in 3) (func (result anyref) (param anyref) get_local 0) (export "" 0))')));`);
+    g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func (result anyref) (param anyref) get_local 0) (export "" 0))')));`);
 })();
 
 (function() {
     var g = newGlobal({newCompartment: true});
     g.parent = this;
 
     let src = `
       (module
-        (gc_feature_opt_in 3)
         (func (export "func") (result anyref) (param $ref anyref)
             get_local $ref
         )
       )
     `;
 
     g.eval(`
         var obj = { somekey: 'somevalue' };
--- a/js/src/jit-test/tests/wasm/gc/directives.txt
+++ b/js/src/jit-test/tests/wasm/gc/directives.txt
@@ -1,1 +1,1 @@
-|jit-test| test-also=--wasm-gc; test-also=--wasm-gc --wasm-compiler=ion; test-also=--wasm-gc --wasm-compiler=baseline; include:wasm.js
+|jit-test| test-also=--wasm-gc; test-also=--wasm-compiler=ion; test-also=--wasm-compiler=baseline; test-also=--wasm-gc --wasm-compiler=baseline; include:wasm.js
--- a/js/src/jit-test/tests/wasm/gc/disabled-ref.js
+++ b/js/src/jit-test/tests/wasm/gc/disabled-ref.js
@@ -1,5 +1,5 @@
 // |jit-test| skip-if: wasmGcEnabled()
 
 wasmCompilationShouldFail(
     wasmTextToBinary(`(module (func (param (ref 0)) (unreachable)))`),
-    /reference types not enabled/);
+    /\(ref T\) types not enabled/);
--- a/js/src/jit-test/tests/wasm/gc/disabled.js
+++ b/js/src/jit-test/tests/wasm/gc/disabled.js
@@ -1,24 +1,24 @@
 // |jit-test| skip-if: wasmReftypesEnabled()
 
 const { CompileError, validate } = WebAssembly;
 
 const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /unrecognized opcode|(Structure|reference) types not enabled|invalid inline block type/;
 
 let simpleTests = [
-    "(module (gc_feature_opt_in 3) (func (drop (ref.null))))",
-    "(module (gc_feature_opt_in 3) (func $test (local anyref)))",
-    "(module (gc_feature_opt_in 3) (func $test (param anyref)))",
-    "(module (gc_feature_opt_in 3) (func $test (result anyref) (ref.null)))",
-    "(module (gc_feature_opt_in 3) (func $test (block anyref (unreachable)) unreachable))",
-    "(module (gc_feature_opt_in 3) (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
-    `(module (gc_feature_opt_in 3) (import "a" "b" (param anyref)))`,
-    `(module (gc_feature_opt_in 3) (import "a" "b" (result anyref)))`,
-    `(module (gc_feature_opt_in 3) (type $s (struct)))`,
+    "(module (func (drop (ref.null))))",
+    "(module (func $test (local anyref)))",
+    "(module (func $test (param anyref)))",
+    "(module (func $test (result anyref) (ref.null)))",
+    "(module (func $test (block anyref (unreachable)) unreachable))",
+    "(module (func $test (local anyref) (result i32) (ref.is_null (get_local 0))))",
+    `(module (import "a" "b" (param anyref)))`,
+    `(module (import "a" "b" (result anyref)))`,
+    `(module (type $s (struct)))`,
 ];
 
 // Two distinct failure modes:
 //
 // - if we have no compiled-in support for wasm-gc we'll get a syntax error when
 //   parsing the test programs that use ref types and structures.
 //
 // - if we have compiled-in support for wasm-gc, then there are several cases
--- a/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
+++ b/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
@@ -41,88 +41,77 @@ assertErrorMessage(() => new WebAssembly
                    /GC feature version.*no longer supported by this engine/);
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in ${SOME_FUTURE_VERSION}))`)),
                    WebAssembly.CompileError,
                    /GC feature version is unknown/);
 
-// Parameters of ref type are only available if we opt in.
+// Parameters of anyref type are available regardless of whether we opt in.
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in ${CURRENT_VERSION})
       (type (func (param anyref))))`));
 
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (type (func (param anyref))))`)),
-                   WebAssembly.CompileError,
-                   /reference types not enabled/);
+      (type (func (param anyref))))`));
 
 // Ditto returns
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in ${CURRENT_VERSION})
       (type (func (result anyref))))`));
 
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (type (func (result anyref))))`)),
-                   WebAssembly.CompileError,
-                   /reference types not enabled/);
+      (type (func (result anyref))))`));
 
 // Ditto locals
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in ${CURRENT_VERSION})
       (func (result i32)
        (local anyref)
        (i32.const 0)))`));
 
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+new WebAssembly.Module(wasmTextToBinary(
     `(module
       (func (result i32)
        (local anyref)
-       (i32.const 0)))`)),
-                   WebAssembly.CompileError,
-                   /reference types not enabled/);
+       (i32.const 0)))`));
 
 // Ditto globals
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in ${CURRENT_VERSION})
       (global (mut anyref) (ref.null)))`));
 
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (global (mut anyref) (ref.null)))`)),
-                   WebAssembly.CompileError,
-                   /reference types not enabled/);
+      (global (mut anyref) (ref.null)))`));
 
-// Ref instructions are only available if we opt in.
+// ref.null and ref.is_null are available whetehr we opt in or not, but ref.eq
+// only if we opt in
 //
 // When testing these we need to avoid struct types or parameters, locals,
 // returns, or globals of ref type, or guards on those will preempt the guards
 // on the instructions.
 
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (func ref.null))`)),
-                   WebAssembly.CompileError,
-                   /unrecognized opcode/);
+      (func (result anyref) ref.null))`));
 
-assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (func ref.is_null))`)),
-                   WebAssembly.CompileError,
-                   /unrecognized opcode/);
+      (func (param anyref) (result i32) (ref.is_null (local.get 0))))`));
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
       (func ref.eq))`)),
                    WebAssembly.CompileError,
                    /unrecognized opcode/);
 
--- a/js/src/jit-test/tests/wasm/gc/ion-and-baseline.js
+++ b/js/src/jit-test/tests/wasm/gc/ion-and-baseline.js
@@ -1,35 +1,31 @@
-// |jit-test| skip-if: !wasmReftypesEnabled()
+// |jit-test| skip-if: !wasmGcEnabled()
 
 // Attempt to test intercalls from ion to baseline and back.
 //
 // We get into this situation when the modules are compiled with different
 // tiering policies, or one tiers up before the other, or (for now) one opts
 // into gc and is baseline-compiled and the other does not and is ion-compiled.
 // There are lots of variables here.  Generally, a small module will be
 // ion-compiled unless there's reason to baseline-compile it, so we're likely
 // actually testing something here.
 //
 // Some logging with printf confirms that refmod is baseline-compiled and
 // nonrefmod is ion-compiled at present, with --wasm-gc enabled.
 
-// Ion can't talk about references yet *but* we can call indirect from Ion to a
-// baseline module that has exported a function that accepts or returns anyref,
-// without the caller knowing this or having to declare it.  All such calls
-// should fail in an orderly manner with a type mismatch, at the point of the
-// call.
-
 var refmod = new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in 3)
-
       (import $tbl "" "tbl" (table 4 funcref))
       (import $print "" "print" (func (param i32)))
 
+      ;; Just a dummy
+      (type $s (struct (field i32)))
+
       (type $htype (func (param anyref)))
       (type $itype (func (result anyref)))
 
       (elem (i32.const 0) $f $g)
 
       (func $f (param anyref)
        (call $print (i32.const 1)))
 
--- a/js/src/jit-test/tests/wasm/gc/ref-restrict.js
+++ b/js/src/jit-test/tests/wasm/gc/ref-restrict.js
@@ -54,65 +54,61 @@ assertErrorMessage(() => wasmCompile(
       (gc_feature_opt_in 3)
       (type $box (struct (field $x i32)))
       (func (export "f") (param (ref $box)) (unreachable)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (func (export "f") (param anyref) (unreachable)))`),
          "object");
 
 // Exported function can't return ref result, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
       (gc_feature_opt_in 3)
       (type $box (struct (field $x i32)))
       (func (export "f") (result (ref $box)) (ref.null)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (func (export "f") (result anyref) (ref.null)))`),
          "object");
 
 // Imported function can't take ref parameter, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
       (gc_feature_opt_in 3)
       (type $box (struct (field $x i32)))
       (import "m" "f" (param (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (import "m" "f" (param anyref)))`),
          "object");
 
 // Imported function can't return ref type, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
       (gc_feature_opt_in 3)
       (type $box (struct (field $x i32)))
       (import "m" "f" (param i32) (result (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (import "m" "f" (param i32) (result anyref)))`),
          "object");
 
 // Imported global can't be of Ref type (irrespective of mutability), though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
       (gc_feature_opt_in 3)
@@ -126,23 +122,21 @@ assertErrorMessage(() => wasmCompile(
       (gc_feature_opt_in 3)
       (type $box (struct (field $val i32)))
       (import "m" "g" (global (ref $box))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (import "m" "g" (global (mut anyref))))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (import "m" "g" (global anyref)))`),
          "object");
 
 // Exported global can't be of Ref type (irrespective of mutability), though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
       (gc_feature_opt_in 3)
@@ -156,23 +150,21 @@ assertErrorMessage(() => wasmCompile(
       (gc_feature_opt_in 3)
       (type $box (struct (field $val i32)))
       (global $boxg (export "box") (ref $box) (ref.null)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (global $boxg (export "box") (mut anyref) (ref.null)))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (global $boxg (export "box") anyref (ref.null)))`),
          "object");
 
 // Exported table cannot reference functions that are exposed for Ref, but anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
     `(module
       (gc_feature_opt_in 3)
@@ -190,25 +182,23 @@ assertErrorMessage(() => wasmCompile(
       (table (export "tbl") 1 funcref)
       (elem (i32.const 0) $f1)
       (func $f1 (result (ref $box)) (ref.null)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (table (export "tbl") 1 funcref)
       (elem (i32.const 0) $f1)
       (func $f1 (param anyref) (unreachable)))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (table (export "tbl") 1 funcref)
       (elem (i32.const 0) $f1)
       (func $f1 (result anyref) (ref.null)))`),
          "object");
 
 // Imported table cannot reference functions that are exposed for Ref, though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
@@ -228,25 +218,23 @@ assertErrorMessage(() => wasmCompile(
       (import "m" "tbl" (table 1 funcref))
       (elem (i32.const 0) $f1)
       (func $f1 (result (ref $box)) (ref.null)))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (import "m" "tbl" (table 1 funcref))
       (elem (i32.const 0) $f1)
       (func $f1 (param anyref) (unreachable)))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (import "m" "tbl" (table 1 funcref))
       (elem (i32.const 0) $f1)
       (func $f1 (result anyref) (ref.null)))`),
          "object");
 
 // Can't call via exported table with type that is exposed for Ref, though anyref is OK.
 
 assertErrorMessage(() => wasmCompile(
@@ -268,26 +256,24 @@ assertErrorMessage(() => wasmCompile(
       (table (export "tbl") 1 funcref)
       (func (param i32) (result (ref $box))
        (call_indirect $fn (get_local 0))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (type $fn (func (param anyref)))
       (table (export "tbl") 1 funcref)
       (func (param i32)
        (call_indirect $fn (ref.null) (get_local 0))))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (type $fn (func (result anyref)))
       (table (export "tbl") 1 funcref)
       (func (param i32) (result anyref)
        (call_indirect $fn (get_local 0))))`),
          "object");
 
 // Can't call via imported table with type that is exposed for Ref, though anyref is OK.
 
@@ -310,26 +296,24 @@ assertErrorMessage(() => wasmCompile(
       (import "m" "tbl" (table 1 funcref))
       (func (param i32) (result (ref $box))
        (call_indirect $fn (get_local 0))))`),
                    WebAssembly.CompileError,
                    /cannot expose reference type/);
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (type $fn (func (param anyref)))
       (import "m" "tbl" (table 1 funcref))
       (func (param i32)
        (call_indirect $fn (ref.null) (get_local 0))))`),
          "object");
 
 assertEq(typeof wasmCompile(
     `(module
-      (gc_feature_opt_in 3)
       (type $fn (func (result anyref)))
       (import "m" "tbl" (table 1 funcref))
       (func (param i32) (result anyref)
        (call_indirect $fn (get_local 0))))`),
          "object");
 
 // We can call via a private table with a type that is exposed for Ref.
 
--- a/js/src/jit-test/tests/wasm/gc/stackmaps1.js
+++ b/js/src/jit-test/tests/wasm/gc/stackmaps1.js
@@ -12,17 +12,16 @@
 // Eventually fn0 will trigger GC and we expect the chain of resulting frames
 // to be traced correctly.  fn2, fn1 and fn0 have some ref-typed args, so
 // there will be traceable stack words to follow, in the sequence of frames.
 
 const {Module,Instance} = WebAssembly;
 
 let t =
   `(module
-     (gc_feature_opt_in 3)
      (import $check3 "" "check3" (func (param anyref) (param anyref) (param anyref)))
      (type $typeOfFn0
            (func (result i32) (param i32) (param anyref) (param i32)
                               (param anyref) (param anyref) (param i32)))
      (table 1 1 funcref)
      (elem (i32.const 0) $fn0)
 
      (import $alloc "" "alloc" (func (result anyref)))
--- a/js/src/jit-test/tests/wasm/gc/stackmaps2.js
+++ b/js/src/jit-test/tests/wasm/gc/stackmaps2.js
@@ -23,17 +23,16 @@
 // slow).
 
 const {Module,Instance} = WebAssembly;
 
 const DEBUG = false;
 
 let t =
   `(module
-     (gc_feature_opt_in 3)
      (type $typeOfFn0
            (func (result i32) (param i32) (param anyref) (param i32)
                               (param anyref) (param anyref) (param i32)))
      (table 1 1 funcref)
      (elem (i32.const 0) $fn0)
 
      (import $alloc "" "alloc" (func (result anyref)))
      (import $quitp "" "quitp" (func (result i32)))
--- a/js/src/jit-test/tests/wasm/gc/stackmaps3.js
+++ b/js/src/jit-test/tests/wasm/gc/stackmaps3.js
@@ -19,17 +19,16 @@
 // implementation works.
 
 const {Module,Instance} = WebAssembly;
 
 const DEBUG = false;
 
 let t =
   `(module
-     (gc_feature_opt_in 3)
      (import $mkCons "" "mkCons" (func (result anyref)
                                        (param anyref) (param anyref)))
      (import $mkBoxedInt "" "mkBoxedInt" (func (result anyref)))
 
      (func $mkNil (result anyref)
        ref.null
      )
 
--- a/js/src/jit-test/tests/wasm/gc/tables-generalized.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized.js
@@ -3,53 +3,48 @@
 ///////////////////////////////////////////////////////////////////////////
 //
 // General table management in wasm
 
 // Wasm: Create table-of-anyref
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 anyref))`));
 
 // Wasm: Import table-of-anyref
 // JS: create table-of-anyref
 
 new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table (import "m" "t") 10 anyref))`)),
                          {m:{t: new WebAssembly.Table({element:"anyref", initial:10})}});
 
 new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (import "m" "t" (table 10 anyref)))`)),
                          {m:{t: new WebAssembly.Table({element:"anyref", initial:10})}});
 
 // Wasm: Export table-of-anyref, initial values shall be null
 
 {
     let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table (export "t") 10 anyref))`)));
     let t = ins.exports.t;
     assertEq(t.length, 10);
     for (let i=0; i < t.length; i++)
         assertEq(t.get(0), null);
 }
 
 // JS: Exported table can be grown, and values are preserved
 
 {
     let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table (export "t") 10 anyref))`)));
     let t = ins.exports.t;
     let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
     for (let i in objs)
         t.set(i, objs[i]);
     ins.exports.t.grow(10);
     assertEq(ins.exports.t.length, 20);
     for (let i in objs)
@@ -57,17 +52,16 @@ new WebAssembly.Instance(new WebAssembly
 }
 
 // Wasm: table.copy between tables of anyref (currently source and destination
 // are both table zero)
 
 {
     let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table (export "t") 10 anyref)
        (func (export "f")
          (table.copy (i32.const 5) (i32.const 0) (i32.const 3))))`)));
     let t = ins.exports.t;
     let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
     for (let i in objs)
         t.set(i, objs[i]);
     ins.exports.f();
@@ -82,52 +76,48 @@ new WebAssembly.Instance(new WebAssembly
     assertEq(t.get(8), objs[8]);
     assertEq(t.get(9), objs[9]);
 }
 
 // Wasm: element segments targeting table-of-anyref is forbidden
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (func $f1 (result i32) (i32.const 0))
        (table 10 anyref)
        (elem (i32.const 0) $f1))`)),
                    WebAssembly.CompileError,
                    /only tables of 'funcref' may have element segments/);
 
 // Wasm: table.init on table-of-anyref is forbidden
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (func $f1 (result i32) (i32.const 0))
        (table 10 anyref)
        (elem passive $f1)
        (func
          (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
                    WebAssembly.CompileError,
                    /only tables of 'funcref' may have element segments/);
 
 // Wasm: table types must match at link time
 
 assertErrorMessage(
     () => new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (import "m" "t" (table 10 anyref)))`)),
                                    {m:{t: new WebAssembly.Table({element:"funcref", initial:10})}}),
     WebAssembly.LinkError,
     /imported table type mismatch/);
 
 // call_indirect cannot reference table-of-anyref
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 anyref)
        (type $t (func (param i32) (result i32)))
        (func (result i32)
          (call_indirect $t (i32.const 37))))`)),
                    WebAssembly.CompileError,
                    /indirect calls must go through a table of 'funcref'/);
 
 ///////////////////////////////////////////////////////////////////////////
@@ -165,33 +155,31 @@ function dummy() { return 37 }
 // table.get and table.set
 
 // table.get in bounds - returns right value type & value
 // table.get out of bounds - fails
 
 {
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (table (export "t") 10 anyref)
            (func (export "f") (param i32) (result anyref)
               (table.get (get_local 0))))`);
     let x = {};
     ins.exports.t.set(0, x);
     assertEq(ins.exports.f(0), x);
     assertEq(ins.exports.f(1), null);
     assertErrorMessage(() => ins.exports.f(10), RangeError, /index out of bounds/);
     assertErrorMessage(() => ins.exports.f(-5), RangeError, /index out of bounds/);
 }
 
 // table.get with non-i32 index - fails validation
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 anyref)
        (func (export "f") (param f64) (result anyref)
          (table.get (get_local 0))))`)),
                    WebAssembly.CompileError,
                    /type mismatch/);
 
 // table.get on table of funcref - fails validation because funcref is not expressible
 // Both with and without anyref support
@@ -201,17 +189,16 @@ assertErrorMessage(() => new WebAssembly
        (table 10 funcref)
        (func (export "f") (param i32)
          (drop (table.get (get_local 0)))))`)),
                    WebAssembly.CompileError,
                    /table.get only on tables of anyref/);
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 funcref)
        (func (export "f") (param i32)
          (drop (table.get (get_local 0)))))`)),
                    WebAssembly.CompileError,
                    /table.get only on tables of anyref/);
 
 // table.get when there are no tables - fails validation
 
@@ -224,17 +211,16 @@ assertErrorMessage(() => new WebAssembly
 
 // table.set in bounds with i32 x anyref - works, no value generated
 // table.set with null - works
 // table.set out of bounds - fails
 
 {
     let ins = wasmEvalText(
         `(module
-           (gc_feature_opt_in 3)
            (table (export "t") 10 anyref)
            (func (export "set_anyref") (param i32) (param anyref)
              (table.set (get_local 0) (get_local 1)))
            (func (export "set_null") (param i32)
              (table.set (get_local 0) (ref.null))))`);
     let x = {};
     ins.exports.set_anyref(3, x);
     assertEq(ins.exports.t.get(3), x);
@@ -244,66 +230,59 @@ assertErrorMessage(() => new WebAssembly
     assertErrorMessage(() => ins.exports.set_anyref(10, x), RangeError, /index out of bounds/);
     assertErrorMessage(() => ins.exports.set_anyref(-1, x), RangeError, /index out of bounds/);
 }
 
 // table.set with non-i32 index - fails validation
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 anyref)
        (func (export "f") (param f64)
          (table.set (get_local 0) (ref.null))))`)),
                    WebAssembly.CompileError,
                    /type mismatch/);
 
 // table.set with non-anyref value - fails validation
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 anyref)
        (func (export "f") (param f64)
          (table.set (i32.const 0) (get_local 0))))`)),
                    WebAssembly.CompileError,
                    /type mismatch/);
 
 // table.set on table of funcref - fails validation
-// We need the gc_feature_opt_in here because of the anyref parameter; if we change
-// that to some other type, it's the validation of that type that fails us.
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (gc_feature_opt_in 3)
       (table 10 funcref)
       (func (export "f") (param anyref)
        (table.set (i32.const 0) (get_local 0))))`)),
                    WebAssembly.CompileError,
                    /table.set only on tables of anyref/);
 
 // table.set when there are no tables - fails validation
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (gc_feature_opt_in 3)
       (func (export "f") (param anyref)
        (table.set (i32.const 0) (get_local 0))))`)),
                    WebAssembly.CompileError,
                    /table index out of range for table.set/);
 
 // we can grow table of anyref
 // table.grow with zero delta - always works even at maximum
 // table.grow with delta - works and returns correct old value
 // table.grow with delta at upper limit - fails
 // table.grow with negative delta - fails
 
 let ins = wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)
       (table (export "t") 10 20 anyref)
       (func (export "grow") (param i32) (result i32)
        (table.grow (get_local 0) (ref.null))))`);
 assertEq(ins.exports.grow(0), 10);
 assertEq(ins.exports.t.length, 10);
 assertEq(ins.exports.grow(1), 10);
 assertEq(ins.exports.t.length, 11);
 assertEq(ins.exports.t.get(10), null);
@@ -322,67 +301,62 @@ assertEq(ins.exports.t.length, 20);
 assertEq(ins.exports.grow(-1), -1);
 assertEq(ins.exports.t.length, 20)
 
 // Special case for private tables without a maximum
 
 {
     let ins = wasmEvalText(
         `(module
-          (gc_feature_opt_in 3)
           (table 10 anyref)
           (func (export "grow") (param i32) (result i32)
            (table.grow (get_local 0) (ref.null))))`);
     assertEq(ins.exports.grow(0), 10);
     assertEq(ins.exports.grow(1), 10);
     assertEq(ins.exports.grow(9), 11);
     assertEq(ins.exports.grow(0), 20);
 }
 
 // Can't grow table of funcref yet
 
 assertErrorMessage(() => wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)     ;; Required because of the 'anyref' null value below
       (table $t 2 funcref)
       (func $f
        (drop (table.grow (i32.const 1) (ref.null)))))`),
                    WebAssembly.CompileError,
                    /table.grow only on tables of anyref/);
 
 // table.grow with non-i32 argument - fails validation
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (table 10 anyref)
        (func (export "f") (param f64)
         (table.grow (get_local 0) (ref.null))))`)),
                    WebAssembly.CompileError,
                    /type mismatch/);
 
 // table.grow when there are no tables - fails validation
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-       (gc_feature_opt_in 3)
        (func (export "f") (param i32)
         (table.grow (get_local 0) (ref.null))))`)),
                    WebAssembly.CompileError,
                    /table index out of range for table.grow/);
 
 // table.size on table of anyref
 
 for (let visibility of ['', '(export "t")', '(import "m" "t")']) {
     let exp = {m:{t: new WebAssembly.Table({element:"anyref",
                                             initial: 10,
                                             maximum: 20})}};
     let ins = wasmEvalText(
         `(module
-          (gc_feature_opt_in 3)
           (table ${visibility} 10 20 anyref)
           (func (export "grow") (param i32) (result i32)
            (table.grow (get_local 0) (ref.null)))
           (func (export "size") (result i32)
            (table.size)))`,
         exp);
     assertEq(ins.exports.grow(0), 10);
     assertEq(ins.exports.size(), 10);
--- a/js/src/jit-test/tests/wasm/gc/tables-multiple.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-multiple.js
@@ -5,17 +5,16 @@
 
 ///////////////////////////////////////////////////////////////////////////
 //
 // Positive tests
 
 // - multiple local tables of misc type: syntax, validation, instantiation
 // - element segments can point to a table
 // - call-indirect can specify a table and will use it
-// - be sure to test table stuff w/o gc_feature_opt_in so that we get ion coverage too
 
 var ins = wasmEvalText(
     `(module
       (table $t1 2 funcref)
       (table $t2 2 funcref)
       (type $ftype (func (param i32) (result i32)))
       (elem $t1 (i32.const 0) $f1 $f2)
       (elem $t2 (i32.const 0) $f3 $f4)
@@ -38,17 +37,16 @@ assertEq(ins.g(0, 10), 13);
 assertEq(ins.g(1, 10), 14);
 
 // - export multiple tables.
 //   note the first and third tables make the export list not start at zero,
 //   and make it sparse
 
 var ins = wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)
       (table $t0 (import "m" "t") 2 funcref)
       (table $t1 (export "t1") 2 funcref)
       (table 1 anyref)
       (table $t2 (export "t2") 3 funcref))`,
     {m:{t: new WebAssembly.Table({element:"funcref", initial:2})}}).exports;
 
 assertEq(ins.t1 instanceof WebAssembly.Table, true);
 assertEq(ins.t1.length, 2);
@@ -59,18 +57,16 @@ assertEq(ins.t2.length, 3);
 // - table.get and table.set can point to a table
 
 var exp = {m:{t0: new WebAssembly.Table({element:"funcref", initial:2}),
               t1: new WebAssembly.Table({element:"anyref", initial:3}),
               t2: new WebAssembly.Table({element:"funcref", initial:4}),
               t3: new WebAssembly.Table({element:"anyref", initial:5})}};
 var ins = wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)
-
       (table $t0 (import "m" "t0") 2 funcref)
       (type $id_i32_t (func (param i32) (result i32)))
       (func $id_i32 (param i32) (result i32) (get_local 0))
       (elem $t0 (i32.const 1) $id_i32)
 
       (table $t1 (import "m" "t1") 3 anyref)
 
       (table $t2 (import "m" "t2") 4 funcref)
@@ -108,18 +104,16 @@ assertEq(exp.m.t3.get(4), x);
 // - table.grow can point to a table
 // - growing a table grows the right table but not the others
 // - table.size on tables other than table 0
 
 var exp = {m:{t0: new WebAssembly.Table({element:"anyref", initial:2}),
               t1: new WebAssembly.Table({element:"anyref", initial:3})}};
 var ins = wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)
-
       (table $t0 (import "m" "t0") 2 anyref)
       (table $t1 (import "m" "t1") 3 anyref)
       (func (export "f") (result i32)
        (table.grow $t1 (i32.const 5) (ref.null)))
       (func (export "size0") (result i32)
        (table.size $t0))
       (func (export "size1") (result i32)
        (table.size $t1)))`,
@@ -131,35 +125,32 @@ assertEq(ins.exports.size0(), 2);
 assertEq(ins.exports.size1(), 8);
 
 // - table.copy can point to tables
 
 var exp = {m:{t0: new WebAssembly.Table({element:"anyref", initial:2}),
               t1: new WebAssembly.Table({element:"anyref", initial:3})}};
 var ins = wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)
-
       (table $t0 (import "m" "t0") 2 anyref)
       (table $t1 (import "m" "t1") 3 anyref)
       (func (export "f") (param $dest i32) (param $src i32) (param $len i32)
        (table.copy $t1 (get_local $dest) $t0 (get_local $src) (get_local $len))))`,
     exp);
 
 exp.m.t0.set(0, {x:0});
 exp.m.t0.set(1, {x:1});
 ins.exports.f(1, 0, 2);
 assertEq(exp.m.t1.get(1), exp.m.t0.get(0));
 assertEq(exp.m.t1.get(2), exp.m.t0.get(1));
 
 // - the table.copy syntax makes sense even in the non-parenthesized case
 
 var ins = wasmEvalText(
     `(module
-      (gc_feature_opt_in 3)
       (table $t0 2 anyref)
       (table $t1 2 anyref)
       (func (export "copy") (param $dest i32) (param $src i32) (param $len i32)
        get_local $dest
        get_local $src
        get_local $len
        table.copy $t1 $t0)
       (func (export "set") (param $n i32) (param $v anyref)
@@ -180,17 +171,16 @@ assertEq(ins.exports.get(1), values[1]);
 // in effect forces a representation for the function in the local table that
 // captures the function's instance, at the latest at the time the copy is