Bug 1497146 part 3 - Convert FormData.jsm to C++ [restore() part] r=peterv,mikedeboer
authorAlphan Chen <alchen@mozilla.com>
Fri, 25 Jan 2019 10:19:04 +0000
changeset 512541 21ad3aeb636f3e28853927b4a7b36262320d6f58
parent 512540 878bd06dc8589af32a22a94eef3a85b2df6a31cf
child 512542 84b052018ef87384a065ac9d92aa6e070fbc626b
push id10566
push userarchaeopteryx@coole-files.de
push dateMon, 28 Jan 2019 12:41:12 +0000
treeherdermozilla-beta@69a3d7c8d04b [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
 {
@@ -125,17 +126,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 long or object) CollectedFormDataValue;
+typedef (DOMString or boolean or object) CollectedFormDataValue;
 
 dictionary CollectedFormData
 {
   record<DOMString, CollectedFormDataValue> id;
   record<DOMString, CollectedFormDataValue> xpath;
   DOMString innerHTML;
   ByteString url;
 };
--- 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"
@@ -317,21 +322,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, OwningStringOrBooleanOrLongOrObject>::EntryType*
+static Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
 AppendEntryToCollectedData(nsINode* aNode, const nsAString& aId,
                            uint16_t& aGeneratedCount,
                            CollectedFormData& aRetVal) {
-  Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry;
+  Record<nsString, OwningStringOrBooleanOrObject>::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 {
@@ -381,17 +386,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, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+    Record<nsString, OwningStringOrBooleanOrObject>::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
@@ -436,17 +441,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, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrObject>::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;
@@ -455,17 +460,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, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrObject>::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) ||
@@ -481,28 +486,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, OwningStringOrBooleanOrLongOrObject>::EntryType*
+              Record<nsString, OwningStringOrBooleanOrObject>::EntryType*
                   entry = AppendEntryToCollectedData(input, id, aGeneratedCount,
                                                      aRetVal);
               entry->mValue.SetAsObject() = &jsval.toObject();
             } else {
               JS_ClearPendingException(aCx);
             }
             continue;
           }
         }
       }
-      Record<nsString, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
           AppendEntryToCollectedData(input, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsString() = value;
     }
   }
 }
 
 /*
   @param aDocument: DOMDocument instance to obtain form data for.
@@ -543,30 +548,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, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrObject>::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;
-      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());
@@ -576,17 +581,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, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
           AppendEntryToCollectedData(select, id, aGeneratedCount, aRetVal);
       entry->mValue.SetAsObject() = &jsval.toObject();
     }
   }
 }
 
 /*
   @param aDocument: DOMDocument instance to obtain form data for.
@@ -625,17 +630,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, OwningStringOrBooleanOrLongOrObject>::EntryType* entry =
+      Record<nsString, OwningStringOrBooleanOrObject>::EntryType* entry =
           AppendEntryToCollectedData(input, id, generatedCount, aRetVal);
       entry->mValue.SetAsString() = value;
       return;
     }
   }
 }
 
 /* static */ void SessionStoreUtils::CollectFormData(
@@ -663,8 +668,308 @@ 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;
+}
\ 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]);
-      }
-    }
-  },
-};