summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/sessionstore/XPathGenerator.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/sessionstore/XPathGenerator.jsm')
-rw-r--r--toolkit/modules/sessionstore/XPathGenerator.jsm119
1 files changed, 119 insertions, 0 deletions
diff --git a/toolkit/modules/sessionstore/XPathGenerator.jsm b/toolkit/modules/sessionstore/XPathGenerator.jsm
new file mode 100644
index 000000000..33f397cdf
--- /dev/null
+++ b/toolkit/modules/sessionstore/XPathGenerator.jsm
@@ -0,0 +1,119 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["XPathGenerator"];
+
+this.XPathGenerator = {
+ // these two hashes should be kept in sync
+ namespaceURIs: {
+ "xhtml": "http://www.w3.org/1999/xhtml",
+ "xul": "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ },
+ namespacePrefixes: {
+ "http://www.w3.org/1999/xhtml": "xhtml",
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul": "xul"
+ },
+
+ /**
+ * Generates an approximate XPath query to an (X)HTML node
+ */
+ generate: function sss_xph_generate(aNode) {
+ // have we reached the document node already?
+ if (!aNode.parentNode)
+ return "";
+
+ // Access localName, namespaceURI just once per node since it's expensive.
+ let nNamespaceURI = aNode.namespaceURI;
+ let nLocalName = aNode.localName;
+
+ let prefix = this.namespacePrefixes[nNamespaceURI] || null;
+ let tag = (prefix ? prefix + ":" : "") + this.escapeName(nLocalName);
+
+ // stop once we've found a tag with an ID
+ if (aNode.id)
+ return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]";
+
+ // count the number of previous sibling nodes of the same tag
+ // (and possible also the same name)
+ let count = 0;
+ let nName = aNode.name || null;
+ for (let n = aNode; (n = n.previousSibling); )
+ if (n.localName == nLocalName && n.namespaceURI == nNamespaceURI &&
+ (!nName || n.name == nName))
+ count++;
+
+ // recurse until hitting either the document node or an ID'd node
+ return this.generate(aNode.parentNode) + "/" + tag +
+ (nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") +
+ (count ? "[" + (count + 1) + "]" : "");
+ },
+
+ /**
+ * Resolves an XPath query generated by XPathGenerator.generate
+ */
+ resolve: function sss_xph_resolve(aDocument, aQuery) {
+ let xptype = Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
+ return aDocument.evaluate(aQuery, aDocument, this.resolveNS, xptype, null).singleNodeValue;
+ },
+
+ /**
+ * Namespace resolver for the above XPath resolver
+ */
+ resolveNS: function sss_xph_resolveNS(aPrefix) {
+ return XPathGenerator.namespaceURIs[aPrefix] || null;
+ },
+
+ /**
+ * @returns valid XPath for the given node (usually just the local name itself)
+ */
+ escapeName: function sss_xph_escapeName(aName) {
+ // we can't just use the node's local name, if it contains
+ // special characters (cf. bug 485482)
+ return /^\w+$/.test(aName) ? aName :
+ "*[local-name()=" + this.quoteArgument(aName) + "]";
+ },
+
+ /**
+ * @returns a properly quoted string to insert into an XPath query
+ */
+ quoteArgument: function sss_xph_quoteArgument(aArg) {
+ if (!/'/.test(aArg))
+ return "'" + aArg + "'";
+ if (!/"/.test(aArg))
+ return '"' + aArg + '"';
+ return "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')";
+ },
+
+ /**
+ * @returns an XPath query to all savable form field nodes
+ */
+ get restorableFormNodes() {
+ // for a comprehensive list of all available <INPUT> types see
+ // https://dxr.mozilla.org/mozilla-central/search?q=kInputTypeTable&redirect=false
+ let ignoreInputs = new Map([
+ ["type", ["password", "hidden", "button", "image", "submit", "reset"]],
+ ["autocomplete", ["off"]]
+ ]);
+ // XXXzeniko work-around until lower-case has been implemented (bug 398389)
+ let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"';
+ let ignores = [];
+ for (let [attrName, attrValues] of ignoreInputs) {
+ for (let attrValue of attrValues)
+ ignores.push(`translate(@${attrName}, ${toLowerCase})='${attrValue}'`);
+ }
+ let ignore = `not(${ignores.join(" or ")})`;
+
+ let formNodesXPath = `//textarea[${ignore}]|//xhtml:textarea[${ignore}]|` +
+ `//select[${ignore}]|//xhtml:select[${ignore}]|` +
+ `//input[${ignore}]|//xhtml:input[${ignore}]`;
+
+ // Special case for about:config's search field.
+ formNodesXPath += '|/xul:window[@id="config"]//xul:textbox[@id="textbox"]';
+
+ delete this.restorableFormNodes;
+ return (this.restorableFormNodes = formNodesXPath);
+ }
+};