diff --git a/browser/components/sessionstore/ContentRestore.jsm b/browser/components/sessionstore/ContentRestore.jsm index 2872538663f0..0ca39150c8a8 100644 --- a/browser/components/sessionstore/ContentRestore.jsm +++ b/browser/components/sessionstore/ContentRestore.jsm @@ -8,8 +8,6 @@ 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", @@ -303,7 +301,7 @@ ContentRestoreInternal.prototype = { // 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. diff --git a/dom/base/Document.h b/dom/base/Document.h index 447aaa1307a7..6bc5463a08ab 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3564,6 +3564,8 @@ class Document : public nsINode, void SetDocTreeHadAudibleMedia(); void SetDocTreeHadPlayRevoked(); + mozilla::dom::XPathEvaluator* XPathEvaluator(); + protected: void DoUpdateSVGUseElementShadowTrees(); @@ -3693,8 +3695,6 @@ class Document : public nsINode, 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 diff --git a/dom/chrome-webidl/SessionStoreUtils.webidl b/dom/chrome-webidl/SessionStoreUtils.webidl index 9376ed246862..7ff9fedf1ac7 100644 --- a/dom/chrome-webidl/SessionStoreUtils.webidl +++ b/dom/chrome-webidl/SessionStoreUtils.webidl @@ -111,6 +111,7 @@ namespace SessionStoreUtils { * Form data encoded in an object. */ CollectedFormData collectFormData(Document document); + boolean restoreFormData(Document document, optional CollectedFormData data); }; dictionary SSScrollPositionDict { diff --git a/dom/xslt/xpath/XPathEvaluator.h b/dom/xslt/xpath/XPathEvaluator.h index a12de5c636d2..7f82319a1ced 100644 --- a/dom/xslt/xpath/XPathEvaluator.h +++ b/dom/xslt/xpath/XPathEvaluator.h @@ -48,6 +48,9 @@ class XPathEvaluator final : public NonRefcountedDOMObject { 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 Evaluate( JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode, @@ -55,10 +58,6 @@ class XPathEvaluator final : public NonRefcountedDOMObject { ErrorResult& rv); private: - XPathExpression* CreateExpression(const nsAString& aExpression, - txIParseContext* aContext, - Document* aDocument, ErrorResult& aRv); - nsWeakPtr mDocument; RefPtr mRecycler; }; diff --git a/dom/xslt/xpath/moz.build b/dom/xslt/xpath/moz.build index c053defaf532..664d3dbc1680 100644 --- a/dom/xslt/xpath/moz.build +++ b/dom/xslt/xpath/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXPORTS.mozilla.dom += [ + 'txIXPathContext.h', 'XPathEvaluator.h', 'XPathExpression.h', 'XPathResult.h', diff --git a/dom/xslt/xpath/txIXPathContext.h b/dom/xslt/xpath/txIXPathContext.h index 19a4b312df6e..5a8156d60478 100644 --- a/dom/xslt/xpath/txIXPathContext.h +++ b/dom/xslt/xpath/txIXPathContext.h @@ -6,7 +6,9 @@ #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; diff --git a/mobile/android/chrome/geckoview/GeckoViewContentChild.js b/mobile/android/chrome/geckoview/GeckoViewContentChild.js index 9e9b0cd8ef0f..756842000e99 100644 --- a/mobile/android/chrome/geckoview/GeckoViewContentChild.js +++ b/mobile/android/chrome/geckoview/GeckoViewContentChild.js @@ -7,7 +7,6 @@ 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", @@ -203,7 +202,7 @@ class GeckoViewContentChild extends GeckoViewChildModule { // 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}); diff --git a/mobile/android/components/SessionStore.js b/mobile/android/components/SessionStore.js index 2d14b25c4aff..f2f375a2bd5f 100644 --- a/mobile/android/components/SessionStore.js +++ b/mobile/android/components/SessionStore.js @@ -9,7 +9,6 @@ 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", @@ -1395,7 +1394,7 @@ SessionStore.prototype = { // 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); }); } }, diff --git a/toolkit/components/sessionstore/SessionStoreUtils.cpp b/toolkit/components/sessionstore/SessionStoreUtils.cpp index 0928c3c6cad6..5be1c291c994 100644 --- a/toolkit/components/sessionstore/SessionStoreUtils.cpp +++ b/toolkit/components/sessionstore/SessionStoreUtils.cpp @@ -3,11 +3,16 @@ * 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" @@ -560,8 +565,8 @@ static void CollectFromSelectElement(JSContext* aCx, Document& aDocument, } bool hasDefaultValue = true; nsTArray 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) { @@ -668,3 +673,297 @@ static void CollectFromXULTextbox(Document& aDocument, 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 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& 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 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 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 arrayObj(aCx, aObject); + uint32_t arrayLength = 0; + if (!JS_GetArrayLength(aCx, arrayObj, &arrayLength)) { + JS_ClearPendingException(aCx); + return; + } + nsTArray array(arrayLength); + for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; arrayIdx++) { + JS::Rooted 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 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 expression( + aDocument.XPathEvaluator()->CreateExpression(aExpression, &parsingContext, + &aDocument, rv)); + if (rv.Failed()) { + return nullptr; + } + RefPtr 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 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 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 diff --git a/toolkit/components/sessionstore/SessionStoreUtils.h b/toolkit/components/sessionstore/SessionStoreUtils.h index f68e286baecf..a33edf4b6162 100644 --- a/toolkit/components/sessionstore/SessionStoreUtils.h +++ b/toolkit/components/sessionstore/SessionStoreUtils.h @@ -54,6 +54,10 @@ class SessionStoreUtils { 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 diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index d887d94fda18..8426ed760dc3 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -241,7 +241,6 @@ EXTRA_JS_MODULES += [ 'SelectParentHelper.jsm', 'ServiceRequest.jsm', 'Services.jsm', - 'sessionstore/FormData.jsm', 'ShortcutUtils.jsm', 'Sqlite.jsm', 'Timer.jsm', diff --git a/toolkit/modules/sessionstore/FormData.jsm b/toolkit/modules/sessionstore/FormData.jsm deleted file mode 100644 index 7cae9d4cf230..000000000000 --- 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]); - } - } - }, -};