Merge mozilla-central to autoland. a=merge CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Sat, 26 Jan 2019 15:52:08 +0200
changeset 515558 a711808372f9d2c51f9b5b046ea62aa7a8d24c26
parent 515552 aee9ebe9b9b2432993e8a3cc1f1cf33ab4e2a240 (current diff)
parent 515555 b08b9f22ad06e55aa83e9c85c74db82c50552094 (diff)
child 515559 4fb9d276e06322d4782507200c9536e23d7f8d37
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -853,16 +853,18 @@ pref("browser.sessionstore.upgradeBackup
 // End-users should not run sessionstore in debug mode
 pref("browser.sessionstore.debug", false);
 // Causes SessionStore to ignore non-final update messages from
 // browser tabs that were not caused by a flush from the parent.
 // This is a testing flag and should not be used by end-users.
 pref("browser.sessionstore.debug.no_auto_updates", false);
 // Forget closed windows/tabs after two weeks
 pref("browser.sessionstore.cleanup.forget_closed_after", 1209600000);
+// Maximum number of bytes of DOMSessionStorage data we collect per origin.
+pref("browser.sessionstore.dom_storage_limit", 2048);
 // Amount of failed SessionFile writes until we restart the worker.
 pref("browser.sessionstore.max_write_failures", 5);
 
 // Whether to warn the user when quitting, even though their tabs will be restored.
 pref("browser.sessionstore.warnOnQuit", false);
 
 // allow META refresh by default
 pref("accessibility.blockautorefresh", false);
--- a/browser/base/content/test/trackingUI/browser_trackingUI_animation.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation.js
@@ -2,44 +2,39 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TP_PREF = "privacy.trackingprotection.enabled";
 const ANIMATIONS_PREF = "toolkit.cosmeticAnimations.enabled";
-const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
 
 // Test that the shield icon animation can be controlled by the cosmetic
 // animations pref and that one of the icons is visible in each case.
 add_task(async function testShieldAnimation() {
   await UrlClassifierTestUtils.addTestTrackers();
   Services.prefs.setBoolPref(TP_PREF, true);
-  Services.prefs.setBoolPref(DTSCBN_PREF, true);
 
   let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
   let animationIcon = document.getElementById("tracking-protection-icon-animatable-image");
   let noAnimationIcon = document.getElementById("tracking-protection-icon");
 
   Services.prefs.setBoolPref(ANIMATIONS_PREF, true);
-  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
-                     waitForContentBlockingEvent(2, tab.linkedBrowser.ownerGlobal)]);
+  await promiseTabLoadEvent(tab, TRACKING_PAGE);
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden when animations are enabled");
   ok(BrowserTestUtils.is_visible(animationIcon), "the animated icon is shown when animations are enabled");
 
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
 
   Services.prefs.setBoolPref(ANIMATIONS_PREF, false);
-  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
-                     waitForContentBlockingEvent(2, tab.linkedBrowser.ownerGlobal)]);
+  await promiseTabLoadEvent(tab, TRACKING_PAGE);
   ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown when animations are disabled");
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden when animations are disabled");
 
   gBrowser.removeCurrentTab();
   Services.prefs.clearUserPref(ANIMATIONS_PREF);
   Services.prefs.clearUserPref(TP_PREF);
-  Services.prefs.clearUserPref(DTSCBN_PREF);
   UrlClassifierTestUtils.cleanupTestTrackers();
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_animation_2.js
@@ -6,32 +6,28 @@
  */
 
 const TP_PREF = "privacy.trackingprotection.enabled";
 const TP_PB_PREF = "privacy.trackingprotection.enabled";
 const NCB_PREF = "network.cookie.cookieBehavior";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const COOKIE_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/cookiePage.html";
-const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
 
 requestLongerTimeout(2);
 
 registerCleanupFunction(function() {
   UrlClassifierTestUtils.cleanupTestTrackers();
   Services.prefs.clearUserPref(TP_PREF);
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(NCB_PREF);
-  Services.prefs.clearUserPref(DTSCBN_PREF);
   Services.prefs.clearUserPref(ContentBlocking.prefIntroCount);
 });
 
 async function testTrackingProtectionAnimation(tabbrowser) {
-  Services.prefs.setBoolPref(DTSCBN_PREF, true);
-
   info("Load a test page not containing tracking elements");
   let benignTab = await BrowserTestUtils.openNewForegroundTab(tabbrowser, BENIGN_PAGE);
   let ContentBlocking = tabbrowser.ownerGlobal.ContentBlocking;
 
   ok(!ContentBlocking.iconBox.hasAttribute("active"), "iconBox not active");
   ok(!ContentBlocking.iconBox.hasAttribute("animate"), "iconBox not animating");
 
   info("Load a test page containing tracking elements");
--- a/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_pbmode_exceptions.js
@@ -1,24 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that sites added to the Tracking Protection whitelist in private
 // browsing mode don't persist once the private browsing window closes.
 
 const TP_PB_PREF = "privacy.trackingprotection.enabled";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
-const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
 var TrackingProtection = null;
 var ContentBlocking = null;
 var browser = null;
 
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref(TP_PB_PREF);
-  Services.prefs.clearUserPref(DTSCBN_PREF);
   ContentBlocking = TrackingProtection = browser = null;
   UrlClassifierTestUtils.cleanupTestTrackers();
 });
 
 function hidden(sel) {
   let win = browser.ownerGlobal;
   let el = win.document.querySelector(sel);
   let display = win.getComputedStyle(el).getPropertyValue("display", null);
@@ -76,32 +74,30 @@ function testTrackingPageUnblocked() {
   ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
 
   ok(hidden("#identity-popup-content-blocking-not-detected"), "blocking not detected label is hidden");
   ok(!hidden("#identity-popup-content-blocking-detected"), "blocking detected label is visible");
 }
 
 add_task(async function testExceptionAddition() {
   await UrlClassifierTestUtils.addTestTrackers();
-  Services.prefs.setBoolPref(DTSCBN_PREF, true);
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
   browser = privateWin.gBrowser;
   let tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser: browser, waitForLoad: true, waitForStateStop: true });
 
   ContentBlocking = browser.ownerGlobal.ContentBlocking;
   ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
   Services.prefs.setBoolPref(TP_PB_PREF, true);
   ok(TrackingProtection.enabled, "TP is enabled after setting the pref");
 
   info("Load a test page containing tracking elements");
-  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
-                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
+  await promiseTabLoadEvent(tab, TRACKING_PAGE);
 
   testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   is(identityPopupState(), "closed", "Identity popup is closed");
 
@@ -127,24 +123,22 @@ add_task(async function testExceptionPer
   ContentBlocking = browser.ownerGlobal.ContentBlocking;
   ok(ContentBlocking, "CB is attached to the private window");
   TrackingProtection = browser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the private window");
 
   ok(TrackingProtection.enabled, "TP is still enabled");
 
   info("Load a test page containing tracking elements");
-  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
-                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
+  await promiseTabLoadEvent(tab, TRACKING_PAGE);
 
   testTrackingPage(tab.ownerGlobal);
 
   info("Disable TP for the page (which reloads the page)");
   let tabReloadPromise = promiseTabLoadEvent(tab);
   clickButton("#tracking-action-unblock");
   is(identityPopupState(), "closed", "Identity popup is closed");
 
-  await Promise.all([tabReloadPromise,
-                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
+  await tabReloadPromise;
   testTrackingPageUnblocked();
 
   privateWin.close();
 });
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state.js
@@ -12,34 +12,32 @@
  *     2) A page with tracking elements is loaded.
  *
  * See also Bugs 1175327, 1043801, 1178985
  */
 
 const TP_PREF = "privacy.trackingprotection.enabled";
 const TP_PB_PREF = "privacy.trackingprotection.pbmode.enabled";
 const TPC_PREF = "network.cookie.cookieBehavior";
-const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const COOKIE_PAGE = "http://not-tracking.example.com/browser/browser/base/content/test/trackingUI/cookiePage.html";
 var ContentBlocking = null;
 var TrackingProtection = null;
 var ThirdPartyCookies = null;
 var tabbrowser = null;
 var gTrackingPageURL = TRACKING_PAGE;
 
 registerCleanupFunction(function() {
   TrackingProtection = ContentBlocking =
     ThirdPartyCookies = tabbrowser = null;
   UrlClassifierTestUtils.cleanupTestTrackers();
   Services.prefs.clearUserPref(TP_PREF);
   Services.prefs.clearUserPref(TP_PB_PREF);
   Services.prefs.clearUserPref(TPC_PREF);
-  Services.prefs.clearUserPref(DTSCBN_PREF);
 });
 
 // This is a special version of "hidden" that doesn't check for item
 // visibility and just asserts the display and opacity attributes.
 // That way we can test elements even when their panel is hidden...
 function hidden(sel) {
   let win = tabbrowser.ownerGlobal;
   let el = win.document.querySelector(sel);
@@ -103,29 +101,30 @@ function areTrackersBlocked(isPrivateBro
   return blockedByTP || blockedByTPC;
 }
 
 function testTrackingPage(window) {
   info("Tracking content must be blocked");
   ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
   ok(!ContentBlocking.content.hasAttribute("hasException"), "content shows no exception");
 
-  let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
-  let blockedByTP = areTrackersBlocked(isWindowPrivate);
+  let isPrivateBrowsing = PrivateBrowsingUtils.isWindowPrivate(window);
+  let blockedByTP = areTrackersBlocked(isPrivateBrowsing);
   is(BrowserTestUtils.is_visible(ContentBlocking.iconBox), blockedByTP,
      "icon box is" + (blockedByTP ? "" : " not") + " visible");
   is(ContentBlocking.iconBox.hasAttribute("active"), blockedByTP,
       "shield is" + (blockedByTP ? "" : " not") + " active");
   ok(!ContentBlocking.iconBox.hasAttribute("hasException"), "icon box shows no exception");
   is(ContentBlocking.iconBox.getAttribute("tooltiptext"),
      blockedByTP ? gNavigatorBundle.getString("trackingProtection.icon.activeTooltip") : "",
      "correct tooltip");
 
   ok(hidden("#tracking-action-block"), "blockButton is hidden");
 
+  let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
   if (isWindowPrivate) {
     ok(hidden("#tracking-action-unblock"), "unblockButton is hidden");
     is(!hidden("#tracking-action-unblock-private"), blockedByTP,
        "unblockButtonPrivate is" + (blockedByTP ? "" : " not") + " visible");
   } else {
     ok(hidden("#tracking-action-unblock-private"), "unblockButtonPrivate is hidden");
     is(!hidden("#tracking-action-unblock"), blockedByTP,
        "unblockButton is" + (blockedByTP ? "" : " not") + " visible");
@@ -214,18 +213,16 @@ async function testContentBlocking(tab) 
   clickButton("#tracking-action-block");
   await tabReloadPromise;
   testTrackingPage(tab.ownerGlobal);
 }
 
 add_task(async function testNormalBrowsing() {
   await UrlClassifierTestUtils.addTestTrackers();
 
-  Services.prefs.setBoolPref(DTSCBN_PREF, true);
-
   tabbrowser = gBrowser;
   let tab = tabbrowser.selectedTab = BrowserTestUtils.addTab(tabbrowser);
 
   ContentBlocking = gBrowser.ownerGlobal.ContentBlocking;
   ok(ContentBlocking, "CB is attached to the browser window");
   TrackingProtection = gBrowser.ownerGlobal.TrackingProtection;
   ok(TrackingProtection, "TP is attached to the browser window");
   is(TrackingProtection.enabled, Services.prefs.getBoolPref(TP_PREF),
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state_all_disabled.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state_all_disabled.js
@@ -3,54 +3,50 @@
 
 "use strict";
 
 const TRACKING_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/trackingPage.html";
 const BENIGN_PAGE = "http://tracking.example.org/browser/browser/base/content/test/trackingUI/benignPage.html";
 const TP_PREF = "privacy.trackingprotection.enabled";
 const COOKIE_PREF = "network.cookie.cookieBehavior";
 const ANIMATIONS_PREF = "toolkit.cosmeticAnimations.enabled";
-const DTSCBN_PREF = "dom.testing.sync-content-blocking-notifications";
 
 // Check that the shield icon is always hidden when all content blocking
 // categories are turned off, even when content blocking is on.
 add_task(async function testContentBlockingAllDisabled() {
   await SpecialPowers.pushPrefEnv({set: [
     [TP_PREF, false],
     [COOKIE_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT],
-    [DTSCBN_PREF, true],
   ]});
   await UrlClassifierTestUtils.addTestTrackers();
 
   let tab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   Services.prefs.setBoolPref(ANIMATIONS_PREF, true);
 
   registerCleanupFunction(function() {
     gBrowser.removeCurrentTab();
     Services.prefs.clearUserPref(ANIMATIONS_PREF);
     UrlClassifierTestUtils.cleanupTestTrackers();
   });
 
   let animationIcon = document.getElementById("tracking-protection-icon-animatable-image");
   let noAnimationIcon = document.getElementById("tracking-protection-icon");
 
-  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
-                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
+  await promiseTabLoadEvent(tab, TRACKING_PAGE);
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
 
   await promiseTabLoadEvent(tab, BENIGN_PAGE);
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
 
   Services.prefs.setBoolPref(ANIMATIONS_PREF, false);
   await promiseTabLoadEvent(tab, TRACKING_PAGE);
   ok(BrowserTestUtils.is_hidden(animationIcon), "the animated icon is hidden");
   ok(BrowserTestUtils.is_hidden(noAnimationIcon), "the default icon is hidden");
 
   // Sanitity check that the shield is showing when at least one blocker is enabled.
   await SpecialPowers.pushPrefEnv({set: [
     [TP_PREF, true],
   ]});
-  await Promise.all([promiseTabLoadEvent(tab, TRACKING_PAGE),
-                     waitForContentBlockingEvent(2, tab.ownerGlobal)]);
+  await promiseTabLoadEvent(tab, TRACKING_PAGE);
   ok(BrowserTestUtils.is_visible(noAnimationIcon), "the default icon is shown");
 });
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -3,18 +3,22 @@
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["ContentRestore"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 
+ChromeUtils.defineModuleGetter(this, "FormData",
+  "resource://gre/modules/FormData.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionHistory",
   "resource://gre/modules/sessionstore/SessionHistory.jsm");
+ChromeUtils.defineModuleGetter(this, "SessionStorage",
+  "resource:///modules/sessionstore/SessionStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
 
 /**
  * This module implements the content side of session restoration. The chrome
  * side is handled by SessionStore.jsm. The functions in this module are called
  * by content-sessionStore.js based on messages received from SessionStore.jsm
  * (or, in one case, based on a "load" event). Each tab has its own
@@ -132,17 +136,17 @@ ContentRestoreInternal.prototype = {
     this._historyListener = listener;
 
     // Make sure to reset the capabilities and attributes in case this tab gets
     // reused.
     SessionStoreUtils.restoreDocShellCapabilities(this.docShell, tabData.disallow);
 
 
     if (tabData.storage && this.docShell instanceof Ci.nsIDocShell) {
-      SessionStoreUtils.restoreSessionStorage(this.docShell, tabData.storage);
+      SessionStorage.restore(this.docShell, tabData.storage);
       delete tabData.storage;
     }
 
     // Add a progress listener to correctly handle browser.loadURI()
     // calls from foreign code.
     this._progressListener = new ProgressListener(this.docShell, {
       onStartRequest: () => {
         // Some code called browser.loadURI() on a pending tab. It's safe to
@@ -294,17 +298,17 @@ ContentRestoreInternal.prototype = {
 
     let window = this.docShell.domWindow;
 
     // Restore form data.
     Utils.restoreFrameTreeData(window, formdata, (frame, data) => {
       // restore() will return false, and thus abort restoration for the
       // current |frame| and its descendants, if |data.url| is given but
       // doesn't match the loaded document's URL.
-      return SessionStoreUtils.restoreFormData(frame.document, data);
+      return FormData.restore(frame, data);
     });
 
     // Restore scroll data.
     Utils.restoreFrameTreeData(window, scrollPositions, (frame, data) => {
       if (data.scroll) {
         SessionStoreUtils.restoreScrollPosition(frame, data);
       }
     });
--- a/browser/components/sessionstore/ContentSessionStore.jsm
+++ b/browser/components/sessionstore/ContentSessionStore.jsm
@@ -13,16 +13,18 @@ ChromeUtils.import("resource://gre/modul
 function debug(msg) {
   Services.console.logStringMessage("SessionStoreContent: " + msg);
 }
 
 ChromeUtils.defineModuleGetter(this, "ContentRestore",
   "resource:///modules/sessionstore/ContentRestore.jsm");
 ChromeUtils.defineModuleGetter(this, "SessionHistory",
   "resource://gre/modules/sessionstore/SessionHistory.jsm");
+ChromeUtils.defineModuleGetter(this, "SessionStorage",
+  "resource:///modules/sessionstore/SessionStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "Utils",
   "resource://gre/modules/sessionstore/Utils.jsm");
 
 // A bound to the size of data to store for DOM Storage.
 const DOM_STORAGE_LIMIT_PREF = "browser.sessionstore.dom_storage_limit";
 
 // This pref controls whether or not we send updates to the parent on a timeout
 // or not, and should only be used for tests or debugging.
@@ -526,20 +528,17 @@ class SessionStorageListener extends Han
     }
 
     let {content} = this.mm;
 
     // We need the entire session storage, let's reset the pending individual change
     // messages.
     this.resetChanges();
 
-    this.messageQueue.push("storage", () => {
-      let data = SessionStoreUtils.collectSessionStorage(content);
-      return Object.keys(data).length ? data : null;
-    });
+    this.messageQueue.push("storage", () => SessionStorage.collect(content));
   }
 
   onPageLoadCompleted() {
     this.collect();
   }
 
   onPageLoadStarted() {
     this.resetEventListener();
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/SessionStorage.jsm
@@ -0,0 +1,211 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["SessionStorage"];
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// A bound to the size of data to store for DOM Storage.
+const DOM_STORAGE_LIMIT_PREF = "browser.sessionstore.dom_storage_limit";
+
+// Returns the principal for a given |frame| contained in a given |docShell|.
+function getPrincipalForFrame(docShell, frame) {
+  let ssm = Services.scriptSecurityManager;
+  let uri = frame.document.documentURIObject;
+  return ssm.getDocShellCodebasePrincipal(uri, docShell);
+}
+
+var SessionStorage = Object.freeze({
+  /**
+   * Updates all sessionStorage "super cookies"
+   * @param content
+   *        A tab's global, i.e. the root frame we want to collect for.
+   * @return Returns a nested object that will have hosts as keys and per-origin
+   *         session storage data as strings. For example:
+   *         {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
+   */
+  collect(content) {
+    return SessionStorageInternal.collect(content);
+  },
+
+  /**
+   * Restores all sessionStorage "super cookies".
+   * @param aDocShell
+   *        A tab's docshell (containing the sessionStorage)
+   * @param aStorageData
+   *        A nested object with storage data to be restored that has hosts as
+   *        keys and per-origin session storage data as strings. For example:
+   *        {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
+   */
+  restore(aDocShell, aStorageData) {
+    SessionStorageInternal.restore(aDocShell, aStorageData);
+  },
+});
+
+/**
+ * Calls the given callback |cb|, passing |frame| and each of its descendants.
+ */
+function forEachNonDynamicChildFrame(frame, cb) {
+  // Call for current frame.
+  cb(frame);
+
+  // Call the callback recursively for each descendant.
+  SessionStoreUtils.forEachNonDynamicChildFrame(frame, subframe => {
+    return forEachNonDynamicChildFrame(subframe, cb);
+  });
+}
+
+var SessionStorageInternal = {
+  /**
+   * Reads all session storage data from the given docShell.
+   * @param content
+   *        A tab's global, i.e. the root frame we want to collect for.
+   * @return Returns a nested object that will have hosts as keys and per-origin
+   *         session storage data as strings. For example:
+   *         {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
+   */
+  collect(content) {
+    let data = {};
+    let visitedOrigins = new Set();
+    let docShell = content.docShell;
+
+    forEachNonDynamicChildFrame(content, frame => {
+      let principal = getPrincipalForFrame(docShell, frame);
+      if (!principal) {
+        return;
+      }
+
+      // Get the origin of the current history entry
+      // and use that as a key for the per-principal storage data.
+      let origin;
+      try {
+        // The origin getter may throw for about:blank iframes as of bug 1340710,
+        // but we should ignore them anyway.
+        origin = principal.origin;
+      } catch (e) {
+        return;
+      }
+      if (visitedOrigins.has(origin)) {
+        // Don't read a host twice.
+        return;
+      }
+
+      // Mark the current origin as visited.
+      visitedOrigins.add(origin);
+
+      let originData = this._readEntry(principal, docShell);
+      if (Object.keys(originData).length) {
+        data[origin] = originData;
+      }
+    });
+
+    return Object.keys(data).length ? data : null;
+  },
+
+  /**
+   * Writes session storage data to the given tab.
+   * @param aDocShell
+   *        A tab's docshell (containing the sessionStorage)
+   * @param aStorageData
+   *        A nested object with storage data to be restored that has hosts as
+   *        keys and per-origin session storage data as strings. For example:
+   *        {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
+   */
+  restore(aDocShell, aStorageData) {
+    for (let origin of Object.keys(aStorageData)) {
+      let data = aStorageData[origin];
+
+      let principal;
+
+      try {
+        // NOTE: In capture() we record the full origin for the URI which the
+        // sessionStorage is being captured for. As of bug 1235657 this code
+        // stopped parsing any origins which have originattributes correctly, as
+        // it decided to use the origin attributes from the docshell, and try to
+        // interpret the origin as a URI. Since bug 1353844 this code now correctly
+        // parses the full origin, and then discards the origin attributes, to
+        // make the behavior line up with the original intentions in bug 1235657
+        // while preserving the ability to read all session storage from
+        // previous versions. In the future, if this behavior is desired, we may
+        // want to use the spec instead of the origin as the key, and avoid
+        // transmitting origin attribute information which we then discard when
+        // restoring.
+        //
+        // If changing this logic, make sure to also change the principal
+        // computation logic in SessionStore::_sendRestoreHistory.
+        let attrs = aDocShell.getOriginAttributes();
+        let dataPrincipal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+        principal = Services.scriptSecurityManager.createCodebasePrincipal(dataPrincipal.URI, attrs);
+      } catch (e) {
+        console.error(e);
+        continue;
+      }
+
+      let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
+
+      // There is no need to pass documentURI, it's only used to fill
+      // documentURI property of domstorage event, which in this case has no
+      // consumer. Prevention of events in case of missing documentURI will be
+      // solved in a followup bug to bug 600307.
+      // Null window because the current window doesn't match the principal yet
+      // and loads about:blank.
+      let storage = storageManager.createStorage(null, principal, "", aDocShell.usePrivateBrowsing);
+
+      for (let key of Object.keys(data)) {
+        try {
+          storage.setItem(key, data[key]);
+        } catch (e) {
+          // throws e.g. for URIs that can't have sessionStorage
+          console.error(e);
+        }
+      }
+    }
+  },
+
+  /**
+   * Reads an entry in the session storage data contained in a tab's history.
+   * @param aURI
+   *        That history entry uri
+   * @param aDocShell
+   *        A tab's docshell (containing the sessionStorage)
+   */
+  _readEntry(aPrincipal, aDocShell) {
+    let hostData = {};
+    let storage;
+
+    let window = aDocShell.domWindow;
+
+    try {
+      let storageManager = aDocShell.QueryInterface(Ci.nsIDOMStorageManager);
+      storage = storageManager.getStorage(window, aPrincipal);
+      storage.length; // XXX: Bug 1232955 - storage.length can throw, catch that failure
+    } catch (e) {
+      // sessionStorage might throw if it's turned off, see bug 458954
+      storage = null;
+    }
+
+    if (!storage || !storage.length) {
+      return hostData;
+    }
+
+    // If the DOMSessionStorage contains too much data, ignore it.
+    let usage = window.windowUtils.getStorageUsage(storage);
+    if (usage > Services.prefs.getIntPref(DOM_STORAGE_LIMIT_PREF)) {
+      return hostData;
+    }
+
+    for (let i = 0; i < storage.length; i++) {
+      try {
+        let key = storage.key(i);
+        hostData[key] = storage.getItem(key);
+      } catch (e) {
+        // This currently throws for secured items (cf. bug 442048).
+      }
+    }
+
+    return hostData;
+  },
+};
--- a/browser/components/sessionstore/moz.build
+++ b/browser/components/sessionstore/moz.build
@@ -15,16 +15,17 @@ EXTRA_JS_MODULES.sessionstore = [
     'GlobalState.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'RunState.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionMigration.jsm',
     'SessionSaver.jsm',
     'SessionStartup.jsm',
+    'SessionStorage.jsm',
     'SessionStore.jsm',
     'SessionWorker.js',
     'SessionWorker.jsm',
     'StartupPerformance.jsm',
     'TabAttributes.jsm',
     'TabState.jsm',
     'TabStateCache.jsm',
     'TabStateFlusher.jsm',
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3570,18 +3570,16 @@ class Document : public nsINode,
   // When the doc is blocked permanantly, we would dispatch event to notify
   // front-end side to show blocking icon.
   void MaybeNotifyAutoplayBlocked();
 
   // Sets flags for media autoplay telemetry.
   void SetDocTreeHadAudibleMedia();
   void SetDocTreeHadPlayRevoked();
 
-  mozilla::dom::XPathEvaluator* XPathEvaluator();
-
  protected:
   void DoUpdateSVGUseElementShadowTrees();
 
   already_AddRefed<nsIPrincipal> MaybeDowngradePrincipal(
       nsIPrincipal* aPrincipal);
 
   void EnsureOnloadBlocker();
 
@@ -3701,16 +3699,18 @@ class Document : public nsINode,
   friend class mozAutoSubtreeModified;
 
   virtual Element* GetNameSpaceElement() override { return GetRootElement(); }
 
   void SetContentTypeInternal(const nsACString& aType);
 
   nsCString GetContentTypeInternal() const { return mContentType; }
 
+  mozilla::dom::XPathEvaluator* XPathEvaluator();
+
   // Update our frame request callback scheduling state, if needed.  This will
   // schedule or unschedule them, if necessary, and update
   // mFrameRequestCallbacksScheduled.  aOldShell should only be passed when
   // mPresShell is becoming null; in that case it will be used to get hold of
   // the relevant refresh driver.
   void UpdateFrameRequestCallbackSchedulingState(
       nsIPresShell* aOldShell = nullptr);
 
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1269,17 +1269,17 @@ void nsGlobalWindowInner::FreeInnerObjec
   if (mCleanMessageManager) {
     MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
     if (mChromeFields.mMessageManager) {
       mChromeFields.mMessageManager->Disconnect();
     }
   }
 
   if (mWindowGlobalChild && !mWindowGlobalChild->IsClosed()) {
-    mWindowGlobalChild->Destroy();
+    mWindowGlobalChild->Send__delete__(mWindowGlobalChild);
   }
   mWindowGlobalChild = nullptr;
 
   mIntlUtils = nullptr;
 }
 
 //*****************************************************************************
 // nsGlobalWindowInner::nsISupports
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -314,18 +314,16 @@ using mozilla::TimeStamp;
   }                                                     \
   return GetCurrentInnerWindowInternal()->method args;  \
   PR_END_MACRO
 
 static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter");
 
 static int32_t gOpenPopupSpamCount = 0;
 
-static bool gSyncContentBlockingNotifications = false;
-
 nsGlobalWindowOuter::OuterWindowByIdTable*
     nsGlobalWindowOuter::sOuterWindowsById = nullptr;
 
 /* static */
 nsPIDOMWindowOuter* nsPIDOMWindowOuter::GetFromCurrentInner(
     nsPIDOMWindowInner* aInner) {
   if (!aInner) {
     return nullptr;
@@ -5402,140 +5400,110 @@ void nsGlobalWindowOuter::NotifyContentB
 
   nsCOMPtr<nsIDocShell> docShell = GetDocShell();
   if (!docShell) {
     return;
   }
   nsCOMPtr<Document> doc = docShell->GetDocument();
   NS_ENSURE_TRUE_VOID(doc);
 
-  nsCOMPtr<nsIURI> uri(aURIHint);
-  nsCOMPtr<nsIChannel> channel(aChannel);
-
-  static bool prefInitialized = false;
-  if (!prefInitialized) {
-    Preferences::AddBoolVarCache(
-        &gSyncContentBlockingNotifications,
-        "dom.testing.sync-content-blocking-notifications", false);
-    prefInitialized = true;
-  }
-
-  nsCOMPtr<nsIRunnable> func = NS_NewRunnableFunction(
-      "NotifyContentBlockingEventDelayed",
-      [doc, docShell, uri, channel, aEvent, aBlocked]() {
-        // This event might come after the user has navigated to another
-        // page. To prevent showing the TrackingProtection UI on the wrong
-        // page, we need to check that the loading URI for the channel is
-        // the same as the URI currently loaded in the document.
-        if (!SameLoadingURI(doc, channel) &&
-            aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
-          return;
-        }
-
-        // Notify nsIWebProgressListeners of this content blocking event.
-        // Can be used to change the UI state.
-        nsresult rv = NS_OK;
-        nsCOMPtr<nsISecurityEventSink> eventSink =
-            do_QueryInterface(docShell, &rv);
-        NS_ENSURE_SUCCESS_VOID(rv);
-        uint32_t event = 0;
-        nsCOMPtr<nsISecureBrowserUI> securityUI;
-        docShell->GetSecurityUI(getter_AddRefs(securityUI));
-        if (!securityUI) {
-          return;
-        }
-        securityUI->GetContentBlockingEvent(&event);
-        nsAutoCString origin;
-        nsContentUtils::GetASCIIOrigin(uri, origin);
-
-        bool blockedValue = aBlocked;
-        bool unblocked = false;
-        if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
-          doc->SetHasTrackingContentBlocked(aBlocked, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasTrackingContentBlocked();
-          }
-        } else if (aEvent ==
-                   nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
-          doc->SetHasTrackingContentLoaded(aBlocked, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasTrackingContentLoaded();
-          }
-        } else if (aEvent == nsIWebProgressListener::
-                                 STATE_COOKIES_BLOCKED_BY_PERMISSION) {
-          doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasCookiesBlockedByPermission();
-          }
-        } else if (aEvent ==
-                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
-          doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasTrackingCookiesBlocked();
-          }
-        } else if (aEvent ==
-                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
-          doc->SetHasAllCookiesBlocked(aBlocked, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasAllCookiesBlocked();
-          }
-        } else if (aEvent ==
-                   nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
-          doc->SetHasForeignCookiesBlocked(aBlocked, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasForeignCookiesBlocked();
-          }
-        } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
-          MOZ_ASSERT(!aBlocked,
-                     "We don't expected to see blocked STATE_COOKIES_LOADED");
-          // Note that the logic in this branch is the logical negation of
-          // the logic in other branches, since the Document API we have is
-          // phrased in "loaded" terms as opposed to "blocked" terms.
-          blockedValue = !aBlocked;
-          doc->SetHasCookiesLoaded(blockedValue, origin);
-          if (!aBlocked) {
-            unblocked = !doc->GetHasCookiesLoaded();
-          }
-        } else {
-          // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
-        }
-        const uint32_t oldEvent = event;
-        if (blockedValue) {
-          event |= aEvent;
-        } else if (unblocked) {
-          event &= ~aEvent;
-        }
-
-        if (event == oldEvent
+  // This event might come after the user has navigated to another page.
+  // To prevent showing the TrackingProtection UI on the wrong page, we need to
+  // check that the loading URI for the channel is the same as the URI currently
+  // loaded in the document.
+  if (!SameLoadingURI(doc, aChannel) &&
+      aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
+    return;
+  }
+
+  // Notify nsIWebProgressListeners of this content blocking event.
+  // Can be used to change the UI state.
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell, &rv);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  uint32_t event = 0;
+  nsCOMPtr<nsISecureBrowserUI> securityUI;
+  docShell->GetSecurityUI(getter_AddRefs(securityUI));
+  if (!securityUI) {
+    return;
+  }
+  securityUI->GetContentBlockingEvent(&event);
+  nsAutoCString origin;
+  nsContentUtils::GetASCIIOrigin(aURIHint, origin);
+
+  bool blockedValue = aBlocked;
+  bool unblocked = false;
+  if (aEvent == nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT) {
+    doc->SetHasTrackingContentBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasTrackingContentBlocked();
+    }
+  } else if (aEvent == nsIWebProgressListener::STATE_LOADED_TRACKING_CONTENT) {
+    doc->SetHasTrackingContentLoaded(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasTrackingContentLoaded();
+    }
+  } else if (aEvent ==
+             nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION) {
+    doc->SetHasCookiesBlockedByPermission(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasCookiesBlockedByPermission();
+    }
+  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) {
+    doc->SetHasTrackingCookiesBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasTrackingCookiesBlocked();
+    }
+  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL) {
+    doc->SetHasAllCookiesBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasAllCookiesBlocked();
+    }
+  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
+    doc->SetHasForeignCookiesBlocked(aBlocked, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasForeignCookiesBlocked();
+    }
+  } else if (aEvent == nsIWebProgressListener::STATE_COOKIES_LOADED) {
+    MOZ_ASSERT(!aBlocked,
+               "We don't expected to see blocked STATE_COOKIES_LOADED");
+    // Note that the logic in this branch is the logical negation of the logic
+    // in other branches, since the Document API we have is phrased in
+    // "loaded" terms as opposed to "blocked" terms.
+    blockedValue = !aBlocked;
+    doc->SetHasCookiesLoaded(blockedValue, origin);
+    if (!aBlocked) {
+      unblocked = !doc->GetHasCookiesLoaded();
+    }
+  } else {
+    // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
+  }
+  const uint32_t oldEvent = event;
+  if (blockedValue) {
+    event |= aEvent;
+  } else if (unblocked) {
+    event &= ~aEvent;
+  }
+
+  if (event == oldEvent
 #ifdef ANDROID
-            // GeckoView always needs to notify about blocked trackers,
-            // since the GeckoView API always needs to report the URI and
-            // type of any blocked tracker. We use a platform-dependent code
-            // path here because reporting this notification on desktop
-            // platforms isn't necessary and doing so can have a big
-            // performance cost.
-            && aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
+      // GeckoView always needs to notify about blocked trackers, since the
+      // GeckoView API always needs to report the URI and type of any blocked
+      // tracker.
+      // We use a platform-dependent code path here because reporting this
+      // notification on desktop platforms isn't necessary and doing so can have
+      // a big performance cost.
+      && aEvent != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
 #endif
-        ) {
-          // Avoid dispatching repeated notifications when nothing has
-          // changed
-          return;
-        }
-
-        eventSink->OnContentBlockingEvent(channel, event);
-      });
-  nsresult rv;
-  if (gSyncContentBlockingNotifications) {
-    rv = func->Run();
-  } else {
-    rv = NS_IdleDispatchToCurrentThread(func.forget(), 100);
-  }
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  ) {
+    // Avoid dispatching repeated notifications when nothing has changed
     return;
   }
+
+  eventSink->OnContentBlockingEvent(aChannel, event);
 }
 
 // static
 bool nsGlobalWindowOuter::SameLoadingURI(Document* aDoc, nsIChannel* aChannel) {
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   nsCOMPtr<nsILoadInfo> channelLoadInfo = aChannel->GetLoadInfo();
   if (!channelLoadInfo || !docURI) {
     return false;
--- a/dom/chrome-webidl/SessionStoreUtils.webidl
+++ b/dom/chrome-webidl/SessionStoreUtils.webidl
@@ -106,38 +106,16 @@ namespace SessionStoreUtils {
    *   }
    *
    * @param  doc
    *         DOMDocument instance to obtain form data for.
    * @return object
    *         Form data encoded in an object.
    */
   CollectedFormData collectFormData(Document document);
-  boolean restoreFormData(Document document, optional CollectedFormData data);
-
-  /**
-   * Updates all sessionStorage "super cookies"
-   * @param content
-   *        A tab's global, i.e. the root frame we want to collect for.
-   * @return Returns a nested object that will have hosts as keys and per-origin
-   *         session storage data as strings. For example:
-   *         {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
-   */
-  record<DOMString, record<DOMString, DOMString>> collectSessionStorage(WindowProxy window);
-
-  /**
-   * Restores all sessionStorage "super cookies".
-   * @param aDocShell
-   *        A tab's docshell (containing the sessionStorage)
-   * @param aStorageData
-   *        A nested object with storage data to be restored that has hosts as
-   *        keys and per-origin session storage data as strings. For example:
-   *        {"https://example.com^userContextId=1": {"key": "value", "my_number": "123"}}
-   */
-   void restoreSessionStorage(nsIDocShell docShell, record<DOMString, record<DOMString, DOMString>> data);
 };
 
 dictionary SSScrollPositionDict {
   ByteString scroll;
 };
 
 dictionary CollectedFileListValue
 {
@@ -147,17 +125,17 @@ dictionary CollectedFileListValue
 
 dictionary CollectedNonMultipleSelectValue
 {
   required long selectedIndex;
   required DOMString value;
 };
 
 // object contains either a CollectedFileListValue or a CollectedNonMultipleSelectValue or Sequence<DOMString>
-typedef (DOMString or boolean or object) CollectedFormDataValue;
+typedef (DOMString or boolean or long or object) CollectedFormDataValue;
 
 dictionary CollectedFormData
 {
   record<DOMString, CollectedFormDataValue> id;
   record<DOMString, CollectedFormDataValue> xpath;
   DOMString innerHTML;
   ByteString url;
 };
--- a/dom/ipc/PWindowGlobal.ipdl
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -18,23 +18,20 @@ namespace dom {
  * specifically a |nsGlobalWindowInner|. These actors will form a parent/child
  * link either between the chrome/content process, or will be in-process, for
  * documents which are loaded in the chrome process.
  */
 async protocol PWindowGlobal
 {
   manager PBrowser or PInProcess;
 
-child:
-  async __delete__();
-
 parent:
   /// Update the URI of the document in this WindowGlobal.
   async UpdateDocumentURI(nsIURI aUri);
 
   /// Notify the parent that this PWindowGlobal is now the current global.
   async BecomeCurrentWindowGlobal();
 
-  async Destroy();
+  async __delete__();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -85,34 +85,16 @@ bool WindowGlobalChild::IsCurrentGlobal(
 already_AddRefed<WindowGlobalParent> WindowGlobalChild::GetParentActor() {
   if (mIPCClosed) {
     return nullptr;
   }
   IProtocol* otherSide = InProcessChild::ParentActorFor(this);
   return do_AddRef(static_cast<WindowGlobalParent*>(otherSide));
 }
 
-already_AddRefed<TabChild> WindowGlobalChild::GetTabChild() {
-  if (IsInProcess() || mIPCClosed) {
-    return nullptr;
-  }
-  return do_AddRef(static_cast<TabChild*>(Manager()));
-}
-
-void WindowGlobalChild::Destroy() {
-  // Perform async IPC shutdown unless we're not in-process, and our TabChild is
-  // in the process of being destroyed, which will destroy us as well.
-  RefPtr<TabChild> tabChild = GetTabChild();
-  if (!tabChild || !tabChild->IsDestroyed()) {
-    SendDestroy();
-  }
-
-  mIPCClosed = true;
-}
-
 void WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
   mIPCClosed = true;
   gWindowGlobalChildById->Remove(mInnerWindowId);
 }
 
 WindowGlobalChild::~WindowGlobalChild() {
   MOZ_ASSERT(!gWindowGlobalChildById ||
              !gWindowGlobalChildById->Contains(mInnerWindowId));
--- a/dom/ipc/WindowGlobalChild.h
+++ b/dom/ipc/WindowGlobalChild.h
@@ -37,36 +37,31 @@ class WindowGlobalChild : public nsWrapp
     return GetByInnerWindowId(aInnerWindowId);
   }
 
   dom::BrowsingContext* BrowsingContext() { return mBrowsingContext; }
   nsGlobalWindowInner* WindowGlobal() { return mWindowGlobal; }
 
   // Has this actor been shut down
   bool IsClosed() { return mIPCClosed; }
-  void Destroy();
 
   // Check if this actor is managed by PInProcess, as-in the document is loaded
   // in the chrome process.
   bool IsInProcess() { return XRE_IsParentProcess(); }
 
   // The Window ID for this WindowGlobal
   uint64_t InnerWindowId() { return mInnerWindowId; }
   uint64_t OuterWindowId() { return mOuterWindowId; }
 
   bool IsCurrentGlobal();
 
   // Get the other side of this actor if it is an in-process actor. Returns
   // |nullptr| if the actor has been torn down, or is not in-process.
   already_AddRefed<WindowGlobalParent> GetParentActor();
 
-  // Get this actor's manager if it is not an in-process actor. Returns
-  // |nullptr| if the actor has been torn down, or is in-process.
-  already_AddRefed<TabChild> GetTabChild();
-
   // Create and initialize the WindowGlobalChild object.
   static already_AddRefed<WindowGlobalChild> Create(
       nsGlobalWindowInner* aWindow);
 
   nsISupports* GetParentObject();
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
--- a/dom/ipc/WindowGlobalParent.cpp
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -111,45 +111,28 @@ WindowGlobalParent::GetByInnerWindowId(u
 already_AddRefed<WindowGlobalChild> WindowGlobalParent::GetChildActor() {
   if (mIPCClosed) {
     return nullptr;
   }
   IProtocol* otherSide = InProcessParent::ChildActorFor(this);
   return do_AddRef(static_cast<WindowGlobalChild*>(otherSide));
 }
 
-already_AddRefed<TabParent> WindowGlobalParent::GetTabParent() {
-  if (IsInProcess() || mIPCClosed) {
-    return nullptr;
-  }
-  return do_AddRef(static_cast<TabParent*>(Manager()));
-}
-
 IPCResult WindowGlobalParent::RecvUpdateDocumentURI(nsIURI* aURI) {
   // XXX(nika): Assert that the URI change was one which makes sense (either
   // about:blank -> a real URI, or a legal push/popstate URI change?)
   mDocumentURI = aURI;
   return IPC_OK();
 }
 
 IPCResult WindowGlobalParent::RecvBecomeCurrentWindowGlobal() {
   mBrowsingContext->SetCurrentWindowGlobal(this);
   return IPC_OK();
 }
 
-IPCResult WindowGlobalParent::RecvDestroy() {
-  if (!mIPCClosed) {
-    RefPtr<TabParent> tabParent = GetTabParent();
-    if (!tabParent || !tabParent->IsDestroyed()) {
-      Unused << Send__delete__(this);
-    }
-  }
-  return IPC_OK();
-}
-
 bool WindowGlobalParent::IsCurrentGlobal() {
   return !mIPCClosed && mBrowsingContext->GetCurrentWindowGlobal() == this;
 }
 
 void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
   mIPCClosed = true;
   gWindowGlobalParentsById->Remove(mInnerWindowId);
   mBrowsingContext->UnregisterWindowGlobal(this);
--- a/dom/ipc/WindowGlobalParent.h
+++ b/dom/ipc/WindowGlobalParent.h
@@ -46,20 +46,16 @@ class WindowGlobalParent final : public 
   // Check if this actor is managed by PInProcess, as-in the document is loaded
   // in-process.
   bool IsInProcess() { return mInProcess; }
 
   // Get the other side of this actor if it is an in-process actor. Returns
   // |nullptr| if the actor has been torn down, or is not in-process.
   already_AddRefed<WindowGlobalChild> GetChildActor();
 
-  // Get this actor's manager if it is not an in-process actor. Returns
-  // |nullptr| if the actor has been torn down, or is in-process.
-  already_AddRefed<TabParent> GetTabParent();
-
   // The principal of this WindowGlobal. This value will not change over the
   // lifetime of the WindowGlobal object, even to reflect changes in
   // |document.domain|.
   nsIPrincipal* DocumentPrincipal() { return mDocumentPrincipal; }
 
   // The BrowsingContext which this WindowGlobal has been loaded into.
   ChromeBrowsingContext* BrowsingContext() { return mBrowsingContext; }
 
@@ -89,17 +85,16 @@ class WindowGlobalParent final : public 
   nsISupports* GetParentObject();
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
  protected:
   // IPC messages
   mozilla::ipc::IPCResult RecvUpdateDocumentURI(nsIURI* aURI) override;
   mozilla::ipc::IPCResult RecvBecomeCurrentWindowGlobal() override;
-  mozilla::ipc::IPCResult RecvDestroy() override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
  private:
   ~WindowGlobalParent();
 
   // NOTE: This document principal doesn't reflect possible |document.domain|
   // mutations which may have been made in the actual document.
--- a/dom/xslt/xpath/XPathEvaluator.h
+++ b/dom/xslt/xpath/XPathEvaluator.h
@@ -43,26 +43,27 @@ class XPathEvaluator final : public NonR
   }
   static XPathEvaluator* Constructor(const GlobalObject& aGlobal,
                                      ErrorResult& rv);
   XPathExpression* CreateExpression(const nsAString& aExpression,
                                     XPathNSResolver* aResolver,
                                     ErrorResult& rv);
   XPathExpression* CreateExpression(const nsAString& aExpression,
                                     nsINode* aResolver, ErrorResult& aRv);
-  XPathExpression* CreateExpression(const nsAString& aExpression,
-                                    txIParseContext* aContext,
-                                    Document* aDocument, ErrorResult& aRv);
   nsINode* CreateNSResolver(nsINode& aNodeResolver) { return &aNodeResolver; }
   already_AddRefed<XPathResult> Evaluate(
       JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode,
       XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult,
       ErrorResult& rv);
 
  private:
+  XPathExpression* CreateExpression(const nsAString& aExpression,
+                                    txIParseContext* aContext,
+                                    Document* aDocument, ErrorResult& aRv);
+
   nsWeakPtr mDocument;
   RefPtr<txResultRecycler> mRecycler;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* mozilla_dom_XPathEvaluator_h */
--- a/dom/xslt/xpath/moz.build
+++ b/dom/xslt/xpath/moz.build
@@ -1,16 +1,15 @@
 # -*- 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/.
 
 EXPORTS.mozilla.dom += [
-    'txIXPathContext.h',
     'XPathEvaluator.h',
     'XPathExpression.h',
     'XPathResult.h',
 ]
 
 UNIFIED_SOURCES += [
     'txBooleanExpr.cpp',
     'txBooleanResult.cpp',
--- a/dom/xslt/xpath/txIXPathContext.h
+++ b/dom/xslt/xpath/txIXPathContext.h
@@ -1,19 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __TX_I_XPATH_CONTEXT
 #define __TX_I_XPATH_CONTEXT
 
-#include "nscore.h"
-#include "nsISupportsImpl.h"
-#include "nsStringFwd.h"
+#include "txCore.h"
 
 class FunctionCall;
 class nsAtom;
 class txAExprResult;
 class txResultRecycler;
 class txXPathNode;
 
 /*
--- a/mobile/android/chrome/geckoview/GeckoViewContentChild.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContentChild.js
@@ -2,16 +2,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/. */
 
 ChromeUtils.import("resource://gre/modules/GeckoViewChildModule.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  FormData: "resource://gre/modules/FormData.jsm",
   FormLikeFactory: "resource://gre/modules/FormLikeFactory.jsm",
   GeckoViewAutoFill: "resource://gre/modules/GeckoViewAutoFill.jsm",
   PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
   Services: "resource://gre/modules/Services.jsm",
   SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
 });
 
 class GeckoViewContentChild extends GeckoViewChildModule {
@@ -197,17 +198,17 @@ class GeckoViewContentChild extends Geck
 
           addEventListener("load", _ => {
             const formdata = this._savedState.formdata;
             if (formdata) {
               this.Utils.restoreFrameTreeData(content, formdata, (frame, data) => {
                 // restore() will return false, and thus abort restoration for the
                 // current |frame| and its descendants, if |data.url| is given but
                 // doesn't match the loaded document's URL.
-                return SessionStoreUtils.restoreFormData(frame.document, data);
+                return FormData.restore(frame, data);
               });
             }
           }, {capture: true, mozSystemGroup: true, once: true});
 
           addEventListener("pageshow", _ => {
             const scrolldata = this._savedState.scrolldata;
             if (scrolldata) {
               this.Utils.restoreFrameTreeData(content, scrolldata, (frame, data) => {
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -4,16 +4,17 @@
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   EventDispatcher: "resource://gre/modules/Messaging.jsm",
+  FormData: "resource://gre/modules/FormData.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PrivacyFilter: "resource://gre/modules/sessionstore/PrivacyFilter.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   SessionHistory: "resource://gre/modules/sessionstore/SessionHistory.jsm",
   SharedPreferences: "resource://gre/modules/SharedPreferences.jsm",
   Utils: "resource://gre/modules/sessionstore/Utils.jsm",
 });
 
@@ -1389,17 +1390,17 @@ SessionStore.prototype = {
   */
   _restoreTextData(aFormData, aBrowser) {
     if (aFormData) {
       log("_restoreTextData()");
       Utils.restoreFrameTreeData(aBrowser.contentWindow, aFormData, (frame, data) => {
         // restore() will return false, and thus abort restoration for the
         // current |frame| and its descendants, if |data.url| is given but
         // doesn't match the loaded document's URL.
-        return SessionStoreUtils.restoreFormData(frame.document, data);
+        return FormData.restore(frame, data);
       });
     }
   },
 
   /**
    * Restores the zoom level of the window. This needs to be called before
    * first paint/load (whichever comes first) to take any effect.
    */
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1797,26 +1797,16 @@ PREF("network.predictor.cleaned-up", boo
 // A testing flag.
 VARCACHE_PREF(
   "network.predictor.doing-tests",
    network_predictor_doing_tests,
   bool, false
 )
 
 //---------------------------------------------------------------------------
-// ContentSessionStore prefs
-//---------------------------------------------------------------------------
-// Maximum number of bytes of DOMSessionStorage data we collect per origin.
-VARCACHE_PREF(
-  "browser.sessionstore.dom_storage_limit",
-  browser_sessionstore_dom_storage_limit,
-  uint32_t, 2048
-)
-
-//---------------------------------------------------------------------------
 // Preferences prefs
 //---------------------------------------------------------------------------
 
 PREF("preferences.allow.omt-write", bool, true)
 
 //---------------------------------------------------------------------------
 // Privacy prefs
 //---------------------------------------------------------------------------
--- a/toolkit/components/sessionstore/SessionStoreUtils.cpp
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -1,23 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "js/JSON.h"
-#include "jsapi.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSelectElement.h"
 #include "mozilla/dom/HTMLTextAreaElement.h"
 #include "mozilla/dom/SessionStoreUtils.h"
-#include "mozilla/dom/txIXPathContext.h"
 #include "mozilla/dom/WindowProxyHolder.h"
-#include "mozilla/dom/XPathResult.h"
-#include "mozilla/dom/XPathEvaluator.h"
-#include "mozilla/dom/XPathExpression.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentList.h"
 #include "nsContentUtils.h"
 #include "nsFocusManager.h"
 #include "nsGlobalWindowOuter.h"
 #include "nsIDocShell.h"
 #include "nsIFormControl.h"
 #include "nsIScrollableFrame.h"
@@ -322,21 +317,21 @@ static bool IsValidCCNumber(nsAString& a
   return numLength >= 12 && total % 10 == 0;
 }
 
 // Limit the number of XPath expressions for performance reasons. See bug
 // 477564.
 static const uint16_t kMaxTraversedXPaths = 100;
 
 // A helper function to append a element into mId or mXpath of CollectedFormData
-static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
+static Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType*
 AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
                            uint16_t& aGeneratedCount,
                            CollectedFormData& aRetVal) {
-  Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry;
+  Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry;
   if (!aId.IsEmpty()) {
     if (!aRetVal.mId.WasPassed()) {
       aRetVal.mId.Construct();
     }
     auto& recordEntries = aRetVal.mId.Value().Entries();
     entry = recordEntries.AppendElement();
     entry->mKey = aId;
   } else {
@@ -386,17 +381,17 @@ static void CollectFromTextAreaElement(D
     nsAutoString value;
     textArea->GetValue(value);
     // In order to reduce XPath generation (which is slow), we only save data
     // for form fields that have been changed. (cf. bug 537289)
     if (textArea->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
                               eCaseMatters)) {
       continue;
     }
-    Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+    Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
         AppendEntryToCollectedData(textArea, id, aGeneratedCount, aRetVal);
     entry->mValue.SetAsString() = value;
   }
 }
 
 /*
   @param aDocument: DOMDocument instance to obtain form data for.
   @param aGeneratedCount: the current number of XPath expressions in the
@@ -441,17 +436,17 @@ static void CollectFromInputElement(JSCo
     }
     nsAutoString value;
     if (input->ControlType() == NS_FORM_INPUT_CHECKBOX ||
         input->ControlType() == NS_FORM_INPUT_RADIO) {
       bool checked = input->Checked();
       if (checked == input->DefaultChecked()) {
         continue;
       }
-      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
           AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsBoolean() = checked;
     } else if (input->ControlType() == NS_FORM_INPUT_FILE) {
       IgnoredErrorResult rv;
       nsTArray<nsString> result;
       input->MozGetFileNameArray(result, rv);
       if (rv.Failed() || result.Length() == 0) {
         continue;
@@ -460,17 +455,17 @@ static void CollectFromInputElement(JSCo
       val.mType = NS_LITERAL_STRING("file");
       val.mFileList.SwapElements(result);
 
       JS::Rooted<JS::Value> jsval(aCx);
       if (!ToJSValue(aCx, val, &jsval)) {
         JS_ClearPendingException(aCx);
         continue;
       }
-      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
           AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsObject() = &jsval.toObject();
     } else {
       input->GetValue(value, CallerType::System);
       // In order to reduce XPath generation (which is slow), we only save data
       // for form fields that have been changed. (cf. bug 537289)
       // Also, don't want to collect credit card number.
       if (value.IsEmpty() || IsValidCCNumber(value) ||
@@ -486,28 +481,28 @@ static void CollectFromInputElement(JSCo
         if (id.EqualsLiteral("sessionData")) {
           nsAutoCString url;
           Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
           if (url.EqualsLiteral("about:sessionrestore") ||
               url.EqualsLiteral("about:welcomeback")) {
             JS::Rooted<JS::Value> jsval(aCx);
             if (JS_ParseJSON(aCx, value.get(), value.Length(), &jsval) &&
                 jsval.isObject()) {
-              Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
+              Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType*
                   entry = AppendEntryToCollectedData(input, id, aGeneratedCount,
                                                      aRetVal);
               entry->mValue.SetAsObject() = &jsval.toObject();
             } else {
               JS_ClearPendingException(aCx);
             }
             continue;
           }
         }
       }
-      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
           AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsString() = value;
     }
   }
 }
 
 /*
   @param aDocument: DOMDocument instance to obtain form data for.
@@ -548,30 +543,30 @@ static void CollectFromSelectElement(JSC
       val.mSelectedIndex = select->SelectedIndex();
       val.mValue = selectVal.AsAString();
 
       JS::Rooted<JS::Value> jsval(aCx);
       if (!ToJSValue(aCx, val, &jsval)) {
         JS_ClearPendingException(aCx);
         continue;
       }
-      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
           AppendEntryToCollectedData(select, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsObject() = &jsval.toObject();
     } else {
       // <select>s with the multiple attribute are easier to determine the
       // default value since each <option> has a defaultSelected property
       HTMLOptionsCollection* options = select->GetOptions();
       if (!options) {
         continue;
       }
       bool hasDefaultValue = true;
       nsTArray<nsString> selectslist;
-      uint32_t numOptions = options->Length();
-      for (uint32_t idx = 0; idx < numOptions; idx++) {
+      int numOptions = options->Length();
+      for (int idx = 0; idx < numOptions; idx++) {
         HTMLOptionElement* option = options->ItemAsOption(idx);
         bool selected = option->Selected();
         if (!selected) {
           continue;
         }
         option->GetValue(*selectslist.AppendElement());
         hasDefaultValue =
             hasDefaultValue && (selected == option->DefaultSelected());
@@ -581,17 +576,17 @@ static void CollectFromSelectElement(JSC
       if (hasDefaultValue) {
         continue;
       }
       JS::Rooted<JS::Value> jsval(aCx);
       if (!ToJSValue(aCx, selectslist, &jsval)) {
         JS_ClearPendingException(aCx);
         continue;
       }
-      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
           AppendEntryToCollectedData(select, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsObject() = &jsval.toObject();
     }
   }
 }
 
 /*
   @param aDocument: DOMDocument instance to obtain form data for.
@@ -630,17 +625,17 @@ static void CollectFromXULTextbox(Docume
       }
       input->GetValue(value, CallerType::System);
       if (value.IsEmpty() ||
           input->AttrValueIs(kNameSpaceID_None, nsGkAtoms::value, value,
                              eCaseMatters)) {
         continue;
       }
       uint16_t generatedCount = 0;
-      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
           AppendEntryToCollectedData(input, id, generatedCount, aRetVal);
       entry->mValue.SetAsString() = value;
       return;
     }
   }
 }
 
 /* static */ void SessionStoreUtils::CollectFormData(
@@ -668,479 +663,8 @@ static void CollectFromXULTextbox(Docume
   }
   // Store the frame's current URL with its form data so that we can compare
   // it when restoring data to not inject form data into the wrong document.
   nsIURI* uri = aDocument.GetDocumentURI();
   if (uri) {
     uri->GetSpecIgnoringRef(aRetVal.mUrl.Construct());
   }
 }
-
-MOZ_CAN_RUN_SCRIPT
-static void SetElementAsString(Element* aElement, const nsAString& aValue) {
-  IgnoredErrorResult rv;
-  HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNodeOrNull(aElement);
-  if (textArea) {
-    textArea->SetValue(aValue, rv);
-    if (!rv.Failed()) {
-      nsContentUtils::DispatchInputEvent(aElement);
-    }
-    return;
-  }
-  HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(aElement);
-  if (input) {
-    input->SetValue(aValue, CallerType::NonSystem, rv);
-    if (!rv.Failed()) {
-      nsContentUtils::DispatchInputEvent(aElement);
-      return;
-    }
-  }
-  input = HTMLInputElement::FromNodeOrNull(nsFocusManager::GetRedirectedFocus(aElement));
-  if (input) {
-    input->SetValue(aValue, CallerType::NonSystem, rv);
-    if (!rv.Failed()) {
-      nsContentUtils::DispatchInputEvent(aElement);
-    }
-  }
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetElementAsBool(Element* aElement, bool aValue) {
-  HTMLInputElement* input = HTMLInputElement::FromNodeOrNull(aElement);
-  if (input) {
-    bool checked = input->Checked();
-    if (aValue != checked) {
-      input->SetChecked(aValue);
-      nsContentUtils::DispatchInputEvent(aElement);
-    }
-  }
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetElementAsFiles(HTMLInputElement* aElement,
-                              const CollectedFileListValue& aValue) {
-  nsTArray<nsString> fileList;
-  IgnoredErrorResult rv;
-  aElement->MozSetFileNameArray(aValue.mFileList, rv);
-  if (rv.Failed()) {
-    return;
-  }
-  nsContentUtils::DispatchInputEvent(aElement);
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetElementAsSelect(HTMLSelectElement* aElement,
-                               const CollectedNonMultipleSelectValue& aValue) {
-  HTMLOptionsCollection* options = aElement->GetOptions();
-  if (!options) {
-    return;
-  }
-  int32_t selectIdx = options->SelectedIndex();
-  if (selectIdx >= 0) {
-    nsAutoString selectOptionVal;
-    options->ItemAsOption(selectIdx)->GetValue(selectOptionVal);
-    if (aValue.mValue.Equals(selectOptionVal)) {
-      return;
-    }
-  }
-  uint32_t numOptions = options->Length();
-  for (uint32_t idx = 0; idx < numOptions; idx++) {
-    HTMLOptionElement* option = options->ItemAsOption(idx);
-    nsAutoString optionValue;
-    option->GetValue(optionValue);
-    if (aValue.mValue.Equals(optionValue)) {
-      aElement->SetSelectedIndex(idx);
-      nsContentUtils::DispatchInputEvent(aElement);
-    }
-  }
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetElementAsMultiSelect(HTMLSelectElement* aElement,
-                                    const nsTArray<nsString>& aValueArray) {
-  bool fireEvent = false;
-  HTMLOptionsCollection* options = aElement->GetOptions();
-  if (!options) {
-    return;
-  }
-  uint32_t numOptions = options->Length();
-  for (uint32_t idx = 0; idx < numOptions; idx++) {
-    HTMLOptionElement* option = options->ItemAsOption(idx);
-    nsAutoString optionValue;
-    option->GetValue(optionValue);
-    for (uint32_t i = 0, l = aValueArray.Length(); i < l; ++i) {
-      if (optionValue.Equals(aValueArray[i])) {
-        option->SetSelected(true);
-        if (!option->DefaultSelected()) {
-          fireEvent = true;
-        }
-      }
-    }
-  }
-  if (fireEvent) {
-    nsContentUtils::DispatchInputEvent(aElement);
-  }
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetElementAsObject(JSContext* aCx, Element* aElement,
-                               JS::Handle<JS::Value> aObject) {
-  RefPtr<HTMLInputElement> input = HTMLInputElement::FromNodeOrNull(aElement);
-  if (input) {
-    if (input->ControlType() == NS_FORM_INPUT_FILE) {
-      CollectedFileListValue value;
-      if (value.Init(aCx, aObject)) {
-        SetElementAsFiles(input, value);
-      } else {
-        JS_ClearPendingException(aCx);
-      }
-    }
-    return;
-  }
-  RefPtr<HTMLSelectElement> select = HTMLSelectElement::FromNodeOrNull(aElement);
-  if (select) {
-    // For Single Select Element
-    if (!select->Multiple()) {
-      CollectedNonMultipleSelectValue value;
-      if (value.Init(aCx, aObject)) {
-        SetElementAsSelect(select, value);
-      } else {
-        JS_ClearPendingException(aCx);
-      }
-      return;
-    }
-
-    // For Multiple Selects Element
-    bool isArray = false;
-    JS_IsArrayObject(aCx, aObject, &isArray);
-    if (!isArray) {
-      return;
-    }
-    JS::Rooted<JSObject*> arrayObj(aCx, &aObject.toObject());
-    uint32_t arrayLength = 0;
-    if (!JS_GetArrayLength(aCx, arrayObj, &arrayLength)) {
-      JS_ClearPendingException(aCx);
-      return;
-    }
-    nsTArray<nsString> array(arrayLength);
-    for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) {
-      JS::Rooted<JS::Value> element(aCx);
-      if (!JS_GetElement(aCx, arrayObj, arrayIdx, &element)) {
-        JS_ClearPendingException(aCx);
-        return;
-      }
-      if (!element.isString()) {
-        return;
-      }
-      nsAutoJSString value;
-      if (!value.init(aCx, element)) {
-        JS_ClearPendingException(aCx);
-        return;
-      }
-      array.AppendElement(value);
-    }
-    SetElementAsMultiSelect(select, array);
-  }
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetRestoreData(JSContext* aCx, Element* aElement,
-                           JS::MutableHandle<JS::Value> aObject) {
-  nsAutoString data;
-  if (nsContentUtils::StringifyJSON(aCx, aObject, data)) {
-    SetElementAsString(aElement, data);
-  } else {
-    JS_ClearPendingException(aCx);
-  }
-}
-
-MOZ_CAN_RUN_SCRIPT
-static void SetInnerHTML(Document& aDocument, const CollectedFormData& aData) {
-  RefPtr<Element> bodyElement = aDocument.GetBody();
-  if (aDocument.HasFlag(NODE_IS_EDITABLE) && bodyElement) {
-    IgnoredErrorResult rv;
-    bodyElement->SetInnerHTML(aData.mInnerHTML.Value(),
-                              aDocument.NodePrincipal(), rv);
-    if (!rv.Failed()) {
-      nsContentUtils::DispatchInputEvent(bodyElement);
-    }
-  }
-}
-
-class FormDataParseContext : public txIParseContext {
- public:
-  explicit FormDataParseContext(bool aCaseInsensitive)
-      : mIsCaseInsensitive(aCaseInsensitive) {}
-
-  nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override {
-    if (aPrefix == nsGkAtoms::xul) {
-      aID = kNameSpaceID_XUL;
-    } else {
-      MOZ_ASSERT(nsDependentAtomString(aPrefix).EqualsLiteral("xhtml"));
-      aID = kNameSpaceID_XHTML;
-    }
-    return NS_OK;
-  }
-
-  nsresult resolveFunctionCall(nsAtom* aName, int32_t aID,
-                               FunctionCall** aFunction) override {
-    return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
-  }
-
-  bool caseInsensitiveNameTests() override { return mIsCaseInsensitive; }
-
-  void SetErrorOffset(uint32_t aOffset) override {}
-
- private:
-  bool mIsCaseInsensitive;
-};
-
-static Element* FindNodeByXPath(JSContext* aCx, Document& aDocument,
-                                const nsAString& aExpression) {
-  FormDataParseContext parsingContext(aDocument.IsHTMLDocument());
-  IgnoredErrorResult rv;
-  nsAutoPtr<XPathExpression> expression(
-      aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext,
-                                                   &aDocument, rv));
-  if (rv.Failed()) {
-    return nullptr;
-  }
-  RefPtr<XPathResult> result = expression->Evaluate(
-      aCx, aDocument, XPathResult::FIRST_ORDERED_NODE_TYPE, nullptr, rv);
-  if (rv.Failed()) {
-    return nullptr;
-  }
-  return Element::FromNodeOrNull(result->GetSingleNodeValue(rv));
-}
-
-MOZ_CAN_RUN_SCRIPT_BOUNDARY
-/* static */ bool SessionStoreUtils::RestoreFormData(
-    const GlobalObject& aGlobal, Document& aDocument,
-    const CollectedFormData& aData) {
-  if (!aData.mUrl.WasPassed()) {
-    return true;
-  }
-  // Don't restore any data for the given frame if the URL
-  // stored in the form data doesn't match its current URL.
-  nsAutoCString url;
-  Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
-  if (!aData.mUrl.Value().Equals(url)) {
-    return false;
-  }
-  if (aData.mInnerHTML.WasPassed()) {
-    SetInnerHTML(aDocument, aData);
-  }
-  if (aData.mId.WasPassed()) {
-    for (auto& entry : aData.mId.Value().Entries()) {
-      RefPtr<Element> node = aDocument.GetElementById(entry.mKey);
-      if (entry.mValue.IsString()) {
-        SetElementAsString(node, entry.mValue.GetAsString());
-      } else if (entry.mValue.IsBoolean()) {
-        SetElementAsBool(node, entry.mValue.GetAsBoolean());
-      } else {
-        // For about:{sessionrestore,welcomeback} we saved the field as JSON to
-        // avoid nested instances causing humongous sessionstore.js files.
-        // cf. bug 467409
-        JSContext* cx = aGlobal.Context();
-        if (entry.mKey.EqualsLiteral("sessionData")) {
-          nsAutoCString url;
-          Unused << aDocument.GetDocumentURI()->GetSpecIgnoringRef(url);
-          if (url.EqualsLiteral("about:sessionrestore") ||
-              url.EqualsLiteral("about:welcomeback")) {
-            JS::Rooted<JS::Value> object(
-                cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
-            SetRestoreData(cx, node, &object);
-            continue;
-          }
-        }
-        JS::Rooted<JS::Value> object(
-            cx, JS::ObjectValue(*entry.mValue.GetAsObject()));
-        SetElementAsObject(cx, node, object);
-      }
-    }
-  }
-  if (aData.mXpath.WasPassed()) {
-    for (auto& entry : aData.mXpath.Value().Entries()) {
-      RefPtr<Element> node = FindNodeByXPath(aGlobal.Context(), aDocument, entry.mKey);
-      if (entry.mValue.IsString()) {
-        SetElementAsString(node, entry.mValue.GetAsString());
-      } else if (entry.mValue.IsBoolean()) {
-        SetElementAsBool(node, entry.mValue.GetAsBoolean());
-      } else {
-        JS::Rooted<JS::Value> object(
-            aGlobal.Context(), JS::ObjectValue(*entry.mValue.GetAsObject()));
-        SetElementAsObject(aGlobal.Context(), node, object);
-      }
-    }
-  }
-  return true;
-}
-
-/* Read entries in the session storage data contained in a tab's history. */
-static void ReadAllEntriesFromStorage(
-    nsPIDOMWindowOuter* aWindow,
-    nsTHashtable<nsCStringHashKey>& aVisitedOrigins,
-    Record<nsString, Record<nsString, nsString>>& aRetVal) {
-  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
-  if (!docShell) {
-    return;
-  }
-
-  Document* doc = aWindow->GetDoc();
-  if (!doc) {
-    return;
-  }
-  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
-  if (!principal) {
-    return;
-  }
-
-  nsAutoCString origin;
-  nsresult rv = principal->GetOrigin(origin);
-  if (NS_FAILED(rv) || aVisitedOrigins.Contains(origin)) {
-    // Don't read a host twice.
-    return;
-  }
-
-  /* Completed checking for recursion and is about to read storage*/
-  nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(docShell);
-  if (!storageManager) {
-    return;
-  }
-  RefPtr<Storage> storage;
-  storageManager->GetStorage(aWindow->GetCurrentInnerWindow(), principal, false,
-                             getter_AddRefs(storage));
-  if (!storage) {
-    return;
-  }
-  mozilla::IgnoredErrorResult result;
-  uint32_t len = storage->GetLength(*principal, result);
-  if (result.Failed() || len == 0) {
-    return;
-  }
-  int64_t storageUsage = storage->GetOriginQuotaUsage();
-  if (storageUsage > StaticPrefs::browser_sessionstore_dom_storage_limit()) {
-    return;
-  }
-
-  Record<nsString, Record<nsString, nsString>>::EntryType* recordEntry = nullptr;
-  for (uint32_t i = 0; i < len; i++) {
-    Record<nsString, nsString>::EntryType entry;
-    mozilla::IgnoredErrorResult res;
-    storage->Key(i, entry.mKey, *principal, res);
-    if (res.Failed()) {
-      continue;
-    }
-
-    storage->GetItem(entry.mKey, entry.mValue, *principal, res);
-    if (res.Failed()) {
-      continue;
-    }
-
-    if (!recordEntry) {
-      recordEntry = aRetVal.Entries().AppendElement();
-      recordEntry->mKey = NS_ConvertUTF8toUTF16(origin);
-      aVisitedOrigins.PutEntry(origin);
-    }
-    recordEntry->mValue.Entries().AppendElement(std::move(entry));
-  }
-}
-
-/* Collect Collect session storage from current frame and all child frame */
-static void CollectedSessionStorageInternal(
-    JSContext* aCx, BrowsingContext* aBrowsingContext,
-    nsTHashtable<nsCStringHashKey>& aVisitedOrigins,
-    Record<nsString, Record<nsString, nsString>>& aRetVal) {
-  /* Collect session store from current frame */
-  nsPIDOMWindowOuter* window = aBrowsingContext->GetDOMWindow();
-  if (!window) {
-    return;
-  }
-  ReadAllEntriesFromStorage(window, aVisitedOrigins, aRetVal);
-
-  /* Collect session storage from all child frame */
-  nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
-  if (!docShell) {
-    return;
-  }
-  int32_t length;
-  nsresult rv = docShell->GetChildCount(&length);
-  if (NS_FAILED(rv)) {
-    return;
-  }
-  for (int32_t i = 0; i < length; ++i) {
-    nsCOMPtr<nsIDocShellTreeItem> item;
-    docShell->GetChildAt(i, getter_AddRefs(item));
-    if (!item) {
-      return;
-    }
-    nsCOMPtr<nsIDocShell> childDocShell(do_QueryInterface(item));
-    if (!childDocShell) {
-      return;
-    }
-    bool isDynamic = false;
-    rv = childDocShell->GetCreatedDynamically(&isDynamic);
-    if (NS_SUCCEEDED(rv) && isDynamic) {
-      continue;
-    }
-    CollectedSessionStorageInternal(
-        aCx, nsDocShell::Cast(childDocShell)->GetBrowsingContext(),
-        aVisitedOrigins, aRetVal);
-  }
-}
-
-/* static */ void SessionStoreUtils::CollectSessionStorage(
-    const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
-    Record<nsString, Record<nsString, nsString>>& aRetVal) {
-  nsTHashtable<nsCStringHashKey> visitedOrigins;
-  CollectedSessionStorageInternal(aGlobal.Context(), aWindow.get(),
-                                  visitedOrigins, aRetVal);
-}
-
-/* static */ void SessionStoreUtils::RestoreSessionStorage(
-    const GlobalObject& aGlobal, nsIDocShell* aDocShell,
-    const Record<nsString, Record<nsString, nsString>>& aData) {
-  for (auto& entry : aData.Entries()) {
-    // NOTE: In capture() we record the full origin for the URI which the
-    // sessionStorage is being captured for. As of bug 1235657 this code
-    // stopped parsing any origins which have originattributes correctly, as
-    // it decided to use the origin attributes from the docshell, and try to
-    // interpret the origin as a URI. Since bug 1353844 this code now correctly
-    // parses the full origin, and then discards the origin attributes, to
-    // make the behavior line up with the original intentions in bug 1235657
-    // while preserving the ability to read all session storage from
-    // previous versions. In the future, if this behavior is desired, we may
-    // want to use the spec instead of the origin as the key, and avoid
-    // transmitting origin attribute information which we then discard when
-    // restoring.
-    //
-    // If changing this logic, make sure to also change the principal
-    // computation logic in SessionStore::_sendRestoreHistory.
-
-    // OriginAttributes are always after a '^' character
-    int32_t pos = entry.mKey.RFindChar('^');
-    nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateCodebasePrincipal(
-        NS_ConvertUTF16toUTF8(Substring(entry.mKey, 0, pos)));
-    nsresult rv;
-    nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(aDocShell, &rv);
-    if (NS_FAILED(rv)) {
-      return;
-    }
-    RefPtr<Storage> storage;
-    // There is no need to pass documentURI, it's only used to fill documentURI
-    // property of domstorage event, which in this case has no consumer.
-    // Prevention of events in case of missing documentURI will be solved in a
-    // followup bug to bug 600307.
-    // Null window because the current window doesn't match the principal yet
-    // and loads about:blank.
-    storageManager->CreateStorage(nullptr, principal, EmptyString(), false, getter_AddRefs(storage));
-    if (!storage) {
-      continue;
-    }
-    for (auto& InnerEntry : entry.mValue.Entries()) {
-      IgnoredErrorResult result;
-      storage->SetItem(InnerEntry.mKey, InnerEntry.mValue, *principal, result);
-      if (result.Failed()) {
-        NS_WARNING("storage set item failed!");
-      }
-    }
-  }
-}
--- a/toolkit/components/sessionstore/SessionStoreUtils.h
+++ b/toolkit/components/sessionstore/SessionStoreUtils.h
@@ -49,26 +49,14 @@ class SessionStoreUtils {
                                     SSScrollPositionDict& aRetVal);
 
   static void RestoreScrollPosition(const GlobalObject& aGlobal,
                                     nsGlobalWindowInner& aWindow,
                                     const SSScrollPositionDict& data);
 
   static void CollectFormData(const GlobalObject& aGlobal, Document& aDocument,
                               CollectedFormData& aRetVal);
-
-  MOZ_CAN_RUN_SCRIPT_BOUNDARY
-  static bool RestoreFormData(const GlobalObject& aGlobal, Document& aDocument,
-                              const CollectedFormData& aData);
-
-  static void CollectSessionStorage(
-      const GlobalObject& aGlobal, WindowProxyHolder& aWindow,
-      Record<nsString, Record<nsString, nsString>>& aRetVal);
-
-  static void RestoreSessionStorage(
-      const GlobalObject& aGlobal, nsIDocShell* aDocShell,
-      const Record<nsString, Record<nsString, nsString>>& aData);
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SessionStoreUtils_h
--- a/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_threathit_report.html
@@ -205,20 +205,17 @@ function testOnWindow(aTestData) {
       await BrowserTestUtils.waitForContentEvent(browser, "DOMContentLoaded");
       checkResults(aTestData, expected);
       win.close();
       resolve();
     })();
   });
 }
 SpecialPowers.pushPrefEnv(
-  {"set": [
-    ["browser.safebrowsing.phishing.enabled", true],
-    ["dom.testing.sync-content-blocking-notifications", true],
-  ]},
+  {"set": [["browser.safebrowsing.phishing.enabled", true]]},
   test);
 
 function test() {
   (async function() {
     await classifierHelper.waitForInit();
 
     for (let testData of testDatas) {
       await setupTestData(testData);
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -236,16 +236,17 @@ EXTRA_JS_MODULES += [
     'RemoteController.js',
     'RemoteSecurityUI.jsm',
     'RemoteWebProgress.jsm',
     'ResetProfile.jsm',
     'ResponsivenessMonitor.jsm',
     'SelectParentHelper.jsm',
     'ServiceRequest.jsm',
     'Services.jsm',
+    'sessionstore/FormData.jsm',
     'ShortcutUtils.jsm',
     'Sqlite.jsm',
     'Timer.jsm',
     'Troubleshoot.jsm',
     'UpdateUtils.jsm',
     'WebChannel.jsm',
     'WebProgressChild.jsm',
     'ZipUtils.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -0,0 +1,280 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["FormData"];
+
+/**
+ * Returns whether the given URL very likely has input
+ * fields that contain serialized session store data.
+ */
+function isRestorationPage(url) {
+  return url == "about:sessionrestore" || url == "about:welcomeback";
+}
+
+/**
+ * Returns whether the given form |data| object contains nested restoration
+ * data for a page like about:sessionrestore or about:welcomeback.
+ */
+function hasRestorationData(data) {
+  if (isRestorationPage(data.url) && data.id) {
+    return typeof(data.id.sessionData) == "object";
+  }
+
+  return false;
+}
+
+/**
+ * Returns the given document's current URI and strips
+ * off the URI's anchor part, if any.
+ */
+function getDocumentURI(doc) {
+  return doc.documentURI.replace(/#.*$/, "");
+}
+
+/**
+ * The public API exported by this module that allows to collect
+ * and restore form data for a document and its subframes.
+ */
+var FormData = Object.freeze({
+  restore(frame, data) {
+    return FormDataInternal.restore(frame, data);
+  },
+
+  restoreTree(root, data) {
+    FormDataInternal.restoreTree(root, data);
+  },
+});
+
+/**
+ * This module's internal API.
+ */
+var FormDataInternal = {
+  namespaceURIs: {
+    "xhtml": "http://www.w3.org/1999/xhtml",
+    "xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+  },
+
+  /**
+   * Resolves an XPath query generated by node.generateXPath.
+   */
+  resolve(aDocument, aQuery) {
+    let xptype = aDocument.defaultView.XPathResult.FIRST_ORDERED_NODE_TYPE;
+    return aDocument.evaluate(aQuery, aDocument, this.resolveNS.bind(this), xptype, null).singleNodeValue;
+  },
+
+  /**
+   * Namespace resolver for the above XPath resolver.
+   */
+  resolveNS(aPrefix) {
+    return this.namespaceURIs[aPrefix] || null;
+  },
+
+  /**
+   * @returns an XPath query to all savable form field nodes
+   */
+  get restorableFormNodesXPath() {
+    let formNodesXPath = "//textarea|//xhtml:textarea|" +
+      "//select|//xhtml:select|" +
+      "//input|//xhtml:input" +
+      // Special case for about:config's search field.
+      "|/xul:window[@id='config']//xul:textbox[@id='textbox']";
+
+    delete this.restorableFormNodesXPath;
+    return (this.restorableFormNodesXPath = formNodesXPath);
+  },
+
+  /**
+   * Restores form |data| for the given frame. The data is expected to be in
+   * the same format that FormData.collect() returns.
+   *
+   * @param frame (DOMWindow)
+   *        The frame to restore form data to.
+   * @param data (object)
+   *        An object holding form data.
+   */
+  restore({document: doc}, data) {
+    if (!data.url) {
+      return true;
+    }
+
+    // Don't restore any data for the given frame if the URL
+    // stored in the form data doesn't match its current URL.
+    if (data.url != getDocumentURI(doc)) {
+      return false;
+    }
+
+    // For about:{sessionrestore,welcomeback} we saved the field as JSON to
+    // avoid nested instances causing humongous sessionstore.js files.
+    // cf. bug 467409
+    if (hasRestorationData(data)) {
+      data.id.sessionData = JSON.stringify(data.id.sessionData);
+    }
+
+    if ("id" in data) {
+      let retrieveNode = id => doc.getElementById(id);
+      this.restoreManyInputValues(data.id, retrieveNode);
+    }
+
+    if ("xpath" in data) {
+      let retrieveNode = xpath => this.resolve(doc, xpath);
+      this.restoreManyInputValues(data.xpath, retrieveNode);
+    }
+
+    if ("innerHTML" in data) {
+      if (doc.body && doc.designMode == "on") {
+      // eslint-disable-next-line no-unsanitized/property
+        doc.body.innerHTML = data.innerHTML;
+        this.fireInputEvent(doc.body);
+      }
+    }
+
+    return true;
+  },
+
+  /**
+   * Iterates the given form data, retrieving nodes for all the keys and
+   * restores their appropriate values.
+   *
+   * @param data (object)
+   *        A subset of the form data as collected by FormData.collect(). This
+   *        is either data stored under "id" or under "xpath".
+   * @param retrieve (function)
+   *        The function used to retrieve the input field belonging to a key
+   *        in the given |data| object.
+   */
+  restoreManyInputValues(data, retrieve) {
+    for (let key of Object.keys(data)) {
+      let input = retrieve(key);
+      if (input) {
+        this.restoreSingleInputValue(input, data[key]);
+      }
+    }
+  },
+
+  /**
+   * Restores a given form value to a given DOMNode and takes care of firing
+   * the appropriate DOM event should the input's value change.
+   *
+   * @param  aNode
+   *         DOMNode to set form value on.
+   * @param  aValue
+   *         Value to set form element to.
+   */
+  restoreSingleInputValue(aNode, aValue) {
+    let fireEvent = false;
+
+    if (typeof aValue == "string" && aNode.type != "file") {
+      // Don't dispatch an input event if there is no change.
+      if (aNode.value == aValue) {
+        return;
+      }
+
+      aNode.value = aValue;
+      fireEvent = true;
+    } else if (typeof aValue == "boolean") {
+      // Don't dispatch a change event for no change.
+      if (aNode.checked == aValue) {
+        return;
+      }
+
+      aNode.checked = aValue;
+      fireEvent = true;
+    } else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
+      // Don't dispatch a change event for no change
+      if (aNode.options[aNode.selectedIndex].value == aValue.value) {
+        return;
+      }
+
+      // find first option with matching aValue if possible
+      for (let i = 0; i < aNode.options.length; i++) {
+        if (aNode.options[i].value == aValue.value) {
+          aNode.selectedIndex = i;
+          fireEvent = true;
+          break;
+        }
+      }
+    } else if (aValue && aValue.fileList && aValue.type == "file" &&
+      aNode.type == "file") {
+      try {
+        // FIXME (bug 1122855): This won't work in content processes.
+        aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
+      } catch (e) {
+        Cu.reportError("mozSetFileNameArray: " + e);
+      }
+      fireEvent = true;
+    } else if (Array.isArray(aValue) && aNode.options) {
+      Array.forEach(aNode.options, function(opt, index) {
+        // don't worry about malformed options with same values
+        opt.selected = aValue.indexOf(opt.value) > -1;
+
+        // Only fire the event here if this wasn't selected by default
+        if (!opt.defaultSelected) {
+          fireEvent = true;
+        }
+      });
+    }
+
+    // Fire events for this node if applicable
+    if (fireEvent) {
+      this.fireInputEvent(aNode);
+    }
+  },
+
+  /**
+   * Dispatches an event of type "input" to the given |node|.
+   *
+   * @param node (DOMNode)
+   */
+  fireInputEvent(node) {
+    // "inputType" value hasn't been decided for session restor:
+    // https://github.com/w3c/input-events/issues/30#issuecomment-438693664
+    let event = node.isInputEventTarget ?
+      new node.ownerGlobal.InputEvent("input", {bubbles: true, inputType: ""}) :
+      new node.ownerGlobal.Event("input", {bubbles: true});
+    node.dispatchEvent(event);
+  },
+
+  /**
+   * Restores form data for the current frame hierarchy starting at |root|
+   * using the given form |data|.
+   *
+   * If the given |root| frame's hierarchy doesn't match that of the given
+   * |data| object we will silently discard data for unreachable frames. For
+   * security reasons we will never restore form data to the wrong frames as
+   * we bail out silently if the stored URL doesn't match the frame's current
+   * URL.
+   *
+   * @param root (DOMWindow)
+   * @param data (object)
+   *        {
+   *          formdata: {id: {input1: "value1"}},
+   *          children: [
+   *            {formdata: {id: {input2: "value2"}}},
+   *            null,
+   *            {formdata: {xpath: { ... }}, children: [ ... ]}
+   *          ]
+   *        }
+   */
+  restoreTree(root, data) {
+    // Restore data for the given |root| frame and its descendants. If restore()
+    // returns false this indicates the |data.url| doesn't match the loaded
+    // document URI. We then must ignore this branch for security reasons.
+    if (this.restore(root, data) === false) {
+      return;
+    }
+
+    if (!data.hasOwnProperty("children")) {
+      return;
+    }
+
+    let frames = root.frames;
+    for (let index of Object.keys(data.children)) {
+      if (index < frames.length) {
+        this.restoreTree(frames[index], data.children[index]);
+      }
+    }
+  },
+};