summaryrefslogtreecommitdiff
path: root/browser/components/sessionstore/XPathGenerator.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/sessionstore/XPathGenerator.jsm')
-rw-r--r--browser/components/sessionstore/XPathGenerator.jsm97
1 files changed, 97 insertions, 0 deletions
diff --git a/browser/components/sessionstore/XPathGenerator.jsm b/browser/components/sessionstore/XPathGenerator.jsm
new file mode 100644
index 000000000..d0639ebb4
--- /dev/null
+++ b/browser/components/sessionstore/XPathGenerator.jsm
@@ -0,0 +1,97 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["XPathGenerator"];
+
+this.XPathGenerator = {
+ // these two hashes should be kept in sync
+ namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" },
+ namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" },
+
+ /**
+ * Generates an approximate XPath query to an (X)HTML node
+ */
+ generate: function(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(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(aPrefix) {
+ return XPathGenerator.namespaceURIs[aPrefix] || null;
+ },
+
+ /**
+ * @returns valid XPath for the given node (usually just the local name itself)
+ */
+ escapeName: function(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(aArg) {
+ return !/'/.test(aArg) ? "'" + aArg + "'" :
+ !/"/.test(aArg) ? '"' + aArg + '"' :
+ "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
+ // http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable
+ let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"];
+ // XXXzeniko work-around until lower-case has been implemented (bug 398389)
+ let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"';
+ let ignore = "not(translate(@type, " + toLowerCase + ")='" +
+ ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')";
+ let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" +
+ "//input[" + ignore + "]|//xhtml:input[" + ignore + "]";
+
+ delete this.restorableFormNodes;
+ return (this.restorableFormNodes = formNodesXPath);
+ }
+};