Bug 1497146 part 3 - Convert FormData.jsm to C++ [restore() part] r=peterv,mikedeboer
☠☠ backed out by 930600ca52c6 ☠ ☠
authorAlphan Chen <alchen@mozilla.com>
Thu, 24 Jan 2019 12:53:28 +0000
changeset 455290 a6cc9b15b1e3f464589488b0f490eadd9bb20f74
parent 455289 54afac5c0bb51abc16131e672ce3049cdbd1620b
child 455291 2bc02c7054fee3b0ac24cda88bff6704b209b858
push id111440
push usernbeleuzu@mozilla.com
push dateThu, 24 Jan 2019 21:48:07 +0000
treeherdermozilla-inbound@2d8247673f5d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv, mikedeboer
bugs1497146
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
Bug 1497146 part 3 - Convert FormData.jsm to C++ [restore() part] r=peterv,mikedeboer Differential Revision: https://phabricator.services.mozilla.com/D12672
browser/components/sessionstore/ContentRestore.jsm
dom/base/Document.h
dom/chrome-webidl/SessionStoreUtils.webidl
dom/xslt/xpath/XPathEvaluator.h
dom/xslt/xpath/moz.build
dom/xslt/xpath/txIXPathContext.h
mobile/android/chrome/geckoview/GeckoViewContentChild.js
mobile/android/components/SessionStore.js
toolkit/components/sessionstore/SessionStoreUtils.cpp
toolkit/components/sessionstore/SessionStoreUtils.h
toolkit/modules/moz.build
toolkit/modules/sessionstore/FormData.jsm
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -3,18 +3,16 @@
 * 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");
 
 /**
@@ -298,17 +296,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 FormData.restore(frame, data);
+      return SessionStoreUtils.restoreFormData(frame.document, data);
     });
 
     // Restore scroll data.
     Utils.restoreFrameTreeData(window, scrollPositions, (frame, data) => {
       if (data.scroll) {
         SessionStoreUtils.restoreScrollPosition(frame, data);
       }
     });
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3559,16 +3559,18 @@ 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();
 
@@ -3688,18 +3690,16 @@ 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/chrome-webidl/SessionStoreUtils.webidl
+++ b/dom/chrome-webidl/SessionStoreUtils.webidl
@@ -106,16 +106,17 @@ 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);
 };
 
 dictionary SSScrollPositionDict {
   ByteString scroll;
 };
 
 dictionary CollectedFileListValue
 {
--- a/dom/xslt/xpath/XPathEvaluator.h
+++ b/dom/xslt/xpath/XPathEvaluator.h
@@ -43,27 +43,26 @@ 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,15 +1,16 @@
 # -*- 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,17 +1,19 @@
 /* -*- 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 "txCore.h"
+#include "nscore.h"
+#include "nsISupportsImpl.h"
+#include "nsStringFwd.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,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.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 {
@@ -198,17 +197,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 FormData.restore(frame, data);
+                return SessionStoreUtils.restoreFormData(frame.document, 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,17 +4,16 @@
 "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",
 });
 
@@ -1390,17 +1389,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 FormData.restore(frame, data);
+        return SessionStoreUtils.restoreFormData(frame.document, 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/toolkit/components/sessionstore/SessionStoreUtils.cpp
+++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp
@@ -1,18 +1,23 @@
 /* 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"
@@ -555,18 +560,18 @@ static void CollectFromSelectElement(JSC
       // <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;
-      int numOptions = options->Length();
-      for (int idx = 0; idx < numOptions; idx++) {
+      uint32_t numOptions = options->Length();
+      for (uint32_t 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());
@@ -663,8 +668,302 @@ 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, JSObject* aObject) {
+  JS::RootedValue object(aCx, JS::ObjectValue(*aObject));
+  RefPtr<HTMLInputElement> input = HTMLInputElement::FromNodeOrNull(aElement);
+  if (input) {
+    if (input->ControlType() == NS_FORM_INPUT_FILE) {
+      CollectedFileListValue value;
+      if (value.Init(aCx, object)) {
+        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, object)) {
+        SetElementAsSelect(select, value);
+      } else {
+        JS_ClearPendingException(aCx);
+      }
+      return;
+    }
+
+    // For Multiple Selects Element
+    bool isArray = false;
+    JS_IsArrayObject(aCx, object, &isArray);
+    if (!isArray) {
+      return;
+    }
+    JS::Rooted<JSObject*> arrayObj(aCx, aObject);
+    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, JSObject* aObject) {
+  JS::RootedValue object(aCx, JS::ObjectValue(*aObject));
+  nsAutoString data;
+  if (nsContentUtils::StringifyJSON(aCx, &object, 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")) {
+            SetRestoreData(cx, node, entry.mValue.GetAsObject());
+            continue;
+          }
+        }
+        SetElementAsObject(cx, node, entry.mValue.GetAsObject());
+      }
+    }
+  }
+  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 {
+        SetElementAsObject(aGlobal.Context(), node, entry.mValue.GetAsObject());
+      }
+    }
+  }
+  return true;
+}
\ No newline at end of file
--- a/toolkit/components/sessionstore/SessionStoreUtils.h
+++ b/toolkit/components/sessionstore/SessionStoreUtils.h
@@ -49,14 +49,18 @@ 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);
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SessionStoreUtils_h
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -236,17 +236,16 @@ 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',
deleted file mode 100644
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ /dev/null
@@ -1,280 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"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]);
-      }
-    }
-  },
-};