Bug 726235 - Break out XPath code into separate module. r=zpao
authordietrich@mozilla.com
Fri, 23 Mar 2012 21:47:04 -0400
changeset 93547 f6cbefc9ea3f345bfd1869e96f33257e884905cb
parent 93546 6c2e0e02113b39dc56cd680133a6623a2df4f30c
child 93548 c38c2569a026d41cbe5788d2e95dad7be9817208
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszpao
bugs726235
milestone14.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 726235 - Break out XPath code into separate module. r=zpao
browser/components/sessionstore/src/Makefile.in
browser/components/sessionstore/src/XPathGenerator.jsm
browser/components/sessionstore/src/nsSessionStore.js
--- a/browser/components/sessionstore/src/Makefile.in
+++ b/browser/components/sessionstore/src/Makefile.in
@@ -43,9 +43,12 @@ EXTRA_COMPONENTS = \
   nsSessionStore.manifest \
   $(NULL)
 
 EXTRA_PP_COMPONENTS = \
 	nsSessionStore.js \
 	nsSessionStartup.js \
 	$(NULL)
 
+libs::
+	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/sessionstore
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/src/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/. */
+
+let EXPORTED_SYMBOLS = ["XPathGenerator"];
+
+let 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 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) {
+    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);
+  }
+};
--- a/browser/components/sessionstore/src/nsSessionStore.js
+++ b/browser/components/sessionstore/src/nsSessionStore.js
@@ -138,16 +138,21 @@ XPCOMUtils.defineLazyGetter(this, "NetUt
   return NetUtil;
 });
 
 XPCOMUtils.defineLazyGetter(this, "ScratchpadManager", function() {
   Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
   return ScratchpadManager;
 });
 
+XPCOMUtils.defineLazyGetter(this, "XPathGenerator", function() {
+  Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
+  return XPathGenerator;
+});
+
 XPCOMUtils.defineLazyServiceGetter(this, "CookieSvc",
   "@mozilla.org/cookiemanager;1", "nsICookieManager2");
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
@@ -2221,18 +2226,18 @@ SessionStoreService.prototype = {
   },
 
   /**
    * collect the state of all form elements
    * @param aDocument
    *        document reference
    */
   _collectFormDataForFrame: function sss_collectFormDataForFrame(aDocument) {
-    let formNodes = aDocument.evaluate(XPathHelper.restorableFormNodes, aDocument,
-                                       XPathHelper.resolveNS,
+    let formNodes = aDocument.evaluate(XPathGenerator.restorableFormNodes, aDocument,
+                                       XPathGenerator.resolveNS,
                                        Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
     let node = formNodes.iterateNext();
     if (!node)
       return null;
 
     const MAX_GENERATED_XPATHS = 100;
     let generatedCount = 0;
 
@@ -2283,17 +2288,17 @@ SessionStoreService.prototype = {
       // In order to reduce XPath generation (which is slow), we only save data
       // for form fields that have been changed. (cf. bug 537289)
       if (!hasDefaultValue) {
         if (nId) {
           data["#" + nId] = value;
         }
         else {
           generatedCount++;
-          data[XPathHelper.generate(node)] = value;
+          data[XPathGenerator.generate(node)] = value;
         }
       }
 
     } while ((node = formNodes.iterateNext()));
 
     return data;
   },
 
@@ -3396,17 +3401,17 @@ SessionStoreService.prototype = {
       !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, "");
 
     function restoreFormData(aDocument, aData, aURL) {
       for (let key in aData) {
         if (!hasExpectedURL(aDocument, aURL))
           return;
 
         let node = key.charAt(0) == "#" ? aDocument.getElementById(key.slice(1)) :
-                                          XPathHelper.resolve(aDocument, key);
+                                          XPathGenerator.resolve(aDocument, key);
         if (!node)
           continue;
 
         let eventType;
         let value = aData[key];
 
         // for about:sessionrestore we saved the field as JSON to avoid nested
         // instances causing humongous sessionstore.js files. cf. bug 467409
@@ -4459,108 +4464,16 @@ SessionStoreService.prototype = {
         Services.obs.notifyObservers(null,
                                      "sessionstore-state-write-complete",
                                      "");
       }
     });
   }
 };
 
-let XPathHelper = {
-  // 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 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 XPathHelper.generate
-   */
-  resolve: function sss_xph_resolve(aDocument, aQuery) {
-    let xptype = Ci.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 XPathHelper.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) {
-    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);
-  }
-};
-
 // This is used to help meter the number of restoring tabs. This is the control
 // point for telling the next tab to restore. It gets attached to each gBrowser
 // via gBrowser.addTabsProgressListener
 let gRestoreTabsProgressListener = {
   ss: null,
   onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
     // Ignore state changes on browsers that we've already restored and state
     // changes that aren't applicable.