Merge inbound to mozilla-central a=merge
authorarthur.iakab <aiakab@mozilla.com>
Thu, 07 Mar 2019 23:58:42 +0200
changeset 520832 af29567ecdba
parent 520773 ca64604d4b78 (current diff)
parent 520831 7e5e1c5a692d (diff)
child 520874 d201fac1f664
child 520878 25b446bf18bc
child 520908 3d19ee7bc2d7
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
af29567ecdba / 67.0a1 / 20190307215858 / files
nightly linux64
af29567ecdba / 67.0a1 / 20190307215858 / files
nightly mac
af29567ecdba / 67.0a1 / 20190307215858 / files
nightly win32
af29567ecdba / 67.0a1 / 20190307215858 / files
nightly win64
af29567ecdba / 67.0a1 / 20190307215858 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central a=merge
browser/base/content/tabbrowser.js
browser/components/sessionstore/test/browser_preopened_apptabs.js
devtools/client/debugger/new/test/mochitest/helpers.js
devtools/server/actors/thread.js
ipc/glue/BackgroundUtils.cpp
modules/libpref/init/StaticPrefList.h
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