michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["FormData"]; michael@0: michael@0: const Cu = Components.utils; michael@0: const Ci = Components.interfaces; michael@0: michael@0: Cu.import("resource://gre/modules/Timer.jsm"); michael@0: Cu.import("resource://gre/modules/XPathGenerator.jsm"); michael@0: michael@0: /** michael@0: * Returns whether the given URL very likely has input michael@0: * fields that contain serialized session store data. michael@0: */ michael@0: function isRestorationPage(url) { michael@0: return url == "about:sessionrestore" || url == "about:welcomeback"; michael@0: } michael@0: michael@0: /** michael@0: * Returns whether the given form |data| object contains nested restoration michael@0: * data for a page like about:sessionrestore or about:welcomeback. michael@0: */ michael@0: function hasRestorationData(data) { michael@0: if (isRestorationPage(data.url) && data.id) { michael@0: return typeof(data.id.sessionData) == "object"; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Returns the given document's current URI and strips michael@0: * off the URI's anchor part, if any. michael@0: */ michael@0: function getDocumentURI(doc) { michael@0: return doc.documentURI.replace(/#.*$/, ""); michael@0: } michael@0: michael@0: /** michael@0: * The public API exported by this module that allows to collect michael@0: * and restore form data for a document and its subframes. michael@0: */ michael@0: this.FormData = Object.freeze({ michael@0: collect: function (frame) { michael@0: return FormDataInternal.collect(frame); michael@0: }, michael@0: michael@0: restore: function (frame, data) { michael@0: FormDataInternal.restore(frame, data); michael@0: }, michael@0: michael@0: restoreTree: function (root, data) { michael@0: FormDataInternal.restoreTree(root, data); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * This module's internal API. michael@0: */ michael@0: let FormDataInternal = { michael@0: /** michael@0: * Collect form data for a given |frame| *not* including any subframes. michael@0: * michael@0: * The returned object may have an "id", "xpath", or "innerHTML" key or a michael@0: * combination of those three. Form data stored under "id" is for input michael@0: * fields with id attributes. Data stored under "xpath" is used for input michael@0: * fields that don't have a unique id and need to be queried using XPath. michael@0: * The "innerHTML" key is used for editable documents (designMode=on). michael@0: * michael@0: * Example: michael@0: * { michael@0: * id: {input1: "value1", input3: "value3"}, michael@0: * xpath: { michael@0: * "/xhtml:html/xhtml:body/xhtml:input[@name='input2']" : "value2", michael@0: * "/xhtml:html/xhtml:body/xhtml:input[@name='input4']" : "value4" michael@0: * } michael@0: * } michael@0: * michael@0: * @param doc michael@0: * DOMDocument instance to obtain form data for. michael@0: * @return object michael@0: * Form data encoded in an object. michael@0: */ michael@0: collect: function ({document: doc}) { michael@0: let formNodes = doc.evaluate( michael@0: XPathGenerator.restorableFormNodes, michael@0: doc, michael@0: XPathGenerator.resolveNS, michael@0: Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null michael@0: ); michael@0: michael@0: let node; michael@0: let ret = {}; michael@0: michael@0: // Limit the number of XPath expressions for performance reasons. See michael@0: // bug 477564. michael@0: const MAX_TRAVERSED_XPATHS = 100; michael@0: let generatedCount = 0; michael@0: michael@0: while (node = formNodes.iterateNext()) { michael@0: let hasDefaultValue = true; michael@0: let value; michael@0: michael@0: // Only generate a limited number of XPath expressions for perf reasons michael@0: // (cf. bug 477564) michael@0: if (!node.id && generatedCount > MAX_TRAVERSED_XPATHS) { michael@0: continue; michael@0: } michael@0: michael@0: if (node instanceof Ci.nsIDOMHTMLInputElement || michael@0: node instanceof Ci.nsIDOMHTMLTextAreaElement || michael@0: node instanceof Ci.nsIDOMXULTextBoxElement) { michael@0: switch (node.type) { michael@0: case "checkbox": michael@0: case "radio": michael@0: value = node.checked; michael@0: hasDefaultValue = value == node.defaultChecked; michael@0: break; michael@0: case "file": michael@0: value = { type: "file", fileList: node.mozGetFileNameArray() }; michael@0: hasDefaultValue = !value.fileList.length; michael@0: break; michael@0: default: // text, textarea michael@0: value = node.value; michael@0: hasDefaultValue = value == node.defaultValue; michael@0: break; michael@0: } michael@0: } else if (!node.multiple) { michael@0: // s with the multiple attribute are easier to determine the michael@0: // default value since each