Bug 1362330 - Part 1: Move XPath generation to Node's interface and move all remaining XPathGenerator.jsm functions to FormData. r=mikedeboer, r=smaug
authorBeekill95 <nnn_bikiu0707@yahoo.com>
Thu, 15 Jun 2017 08:49:17 +0700
changeset 367344 72ef9aacfb9117450bc1567b3c85e29fcb2aa49c
parent 367343 a48987725783c0b33ed1688f33952bee4f6c9aa0
child 367345 d1ab638d41801ab83585abbdca76a3bd05121998
push id92194
push usercbook@mozilla.com
push dateWed, 05 Jul 2017 06:15:47 +0000
treeherdermozilla-inbound@098f29ddc693 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer, smaug
bugs1362330
milestone56.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 1362330 - Part 1: Move XPath generation to Node's interface and move all remaining XPathGenerator.jsm functions to FormData. r=mikedeboer, r=smaug MozReview-Commit-ID: Ej45wPeddtQ
dom/base/XPathGenerator.cpp
dom/base/XPathGenerator.h
dom/base/moz.build
dom/base/nsINode.cpp
dom/base/nsINode.h
dom/webidl/Node.webidl
toolkit/modules/moz.build
toolkit/modules/sessionstore/FormData.jsm
toolkit/modules/sessionstore/XPathGenerator.jsm
new file mode 100644
--- /dev/null
+++ b/dom/base/XPathGenerator.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "XPathGenerator.h"
+
+#include "nsGkAtoms.h"
+#include "Element.h"
+#include "nsTArray.h"
+
+/**
+ * Check whether a character is a non-word character. A non-word character is a
+ * character that isn't in ('a'..'z') or in ('A'..'Z') or a number or an underscore.
+ * */
+bool IsNonWordCharacter(const char16_t& aChar)
+{
+  if (((char16_t('A') <= aChar) && (aChar <= char16_t('Z'))) ||
+      ((char16_t('a') <= aChar) && (aChar <= char16_t('z'))) ||
+      ((char16_t('0') <= aChar) && (aChar <= char16_t('9'))) ||
+      (aChar == char16_t('_'))) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+/**
+ * Check whether a string contains a non-word character.
+ * */
+bool ContainNonWordCharacter(const nsAString& aStr)
+{
+  const char16_t* cur = aStr.BeginReading();
+  const char16_t* end = aStr.EndReading();
+  for (; cur < end; ++cur) {
+    if (IsNonWordCharacter(*cur)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+/**
+ * Get the prefix according to the given namespace and assign the result to aResult.
+ * */
+void GetPrefix(const nsINode* aNode, nsAString& aResult)
+{
+  if (aNode->IsXULElement()) {
+    aResult.Assign(NS_LITERAL_STRING("xul"));
+  } else if (aNode->IsHTMLElement()) {
+    aResult.Assign(NS_LITERAL_STRING("xhtml"));
+  }
+}
+
+void GetNameAttribute(const nsINode* aNode, nsAString& aResult)
+{
+  if (aNode->HasName()) {
+    const Element* elem = aNode->AsElement();
+    elem->GetAttr(kNameSpaceID_None, nsGkAtoms::name, aResult);
+  }
+}
+
+/**
+ * Put all sequences of ' in a string in between '," and ",' . And then put
+ * the result string in between concat(' and ').
+ *
+ * For example, a string 'a'' will return result concat('',"'",'a',"''",'')
+ * */
+void GenerateConcatExpression(const nsAString& aStr, nsAString& aResult)
+{
+  const char16_t* cur = aStr.BeginReading();
+  const char16_t* end = aStr.EndReading();
+
+  // Put all sequences of ' in between '," and ",'
+  nsAutoString result;
+  const char16_t* nonQuoteBeginPtr = nullptr;
+  const char16_t* quoteBeginPtr = nullptr;
+  for (; cur < end; ++cur) {
+    if (char16_t('\'') == *cur) {
+      if (nonQuoteBeginPtr) {
+        result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr);
+        nonQuoteBeginPtr = nullptr;
+      }
+      if (!quoteBeginPtr) {
+        result.Append(NS_LITERAL_STRING("\',\""));
+        quoteBeginPtr = cur;
+      }
+    } else {
+      if (!nonQuoteBeginPtr) {
+        nonQuoteBeginPtr = cur;
+      }
+      if (quoteBeginPtr) {
+        result.Append(quoteBeginPtr, cur - quoteBeginPtr);
+        result.Append(NS_LITERAL_STRING("\",\'"));
+        quoteBeginPtr = nullptr;
+      }
+    }
+  }
+
+  if (quoteBeginPtr) {
+    result.Append(quoteBeginPtr, cur - quoteBeginPtr);
+    result.Append(NS_LITERAL_STRING("\",\'"));
+  } else if (nonQuoteBeginPtr) {
+    result.Append(nonQuoteBeginPtr, cur - nonQuoteBeginPtr);
+  }
+
+  // Prepend concat(' and append ').
+  aResult.Assign(NS_LITERAL_STRING("concat(\'") + result + NS_LITERAL_STRING("\')"));
+}
+
+void XPathGenerator::QuoteArgument(const nsAString& aArg, nsAString& aResult)
+{
+  if (!aArg.Contains('\'')) {
+    aResult.Assign(NS_LITERAL_STRING("\'") + aArg + NS_LITERAL_STRING("\'"));
+  } else if (!aArg.Contains('\"')) {
+    aResult.Assign(NS_LITERAL_STRING("\"") + aArg + NS_LITERAL_STRING("\""));
+  } else {
+    GenerateConcatExpression(aArg, aResult);
+  }
+}
+
+void XPathGenerator::EscapeName(const nsAString& aName, nsAString& aResult)
+{
+  if (ContainNonWordCharacter(aName)) {
+    nsAutoString quotedArg;
+    QuoteArgument(aName, quotedArg);
+    aResult.Assign(NS_LITERAL_STRING("*[local-name()=") + quotedArg + NS_LITERAL_STRING("]"));
+  } else {
+    aResult.Assign(aName);
+  }
+}
+
+void XPathGenerator::Generate(const nsINode* aNode, nsAString& aResult)
+{
+  if (!aNode->GetParentNode()) {
+    aResult.Truncate();
+    return;
+  }
+
+  nsAutoString nodeNamespaceURI;
+  aNode->GetNamespaceURI(nodeNamespaceURI);
+  const nsString& nodeLocalName = aNode->LocalName();
+
+  nsAutoString prefix;
+  nsAutoString tag;
+  nsAutoString nodeEscapeName;
+  GetPrefix(aNode, prefix);
+  EscapeName(nodeLocalName, nodeEscapeName);
+  if (prefix.IsEmpty()) {
+    tag.Assign(nodeEscapeName);
+  } else {
+    tag.Assign(prefix + NS_LITERAL_STRING(":") + nodeEscapeName);
+  }
+
+  if (aNode->HasID()) {
+    // this must be an element
+    const Element* elem = aNode->AsElement();
+    nsAutoString elemId;
+    nsAutoString quotedArgument;
+    elem->GetId(elemId);
+    QuoteArgument(elemId, quotedArgument);
+    aResult.Assign(NS_LITERAL_STRING("//") + tag + NS_LITERAL_STRING("[@id=") +
+                   quotedArgument + NS_LITERAL_STRING("]"));
+    return;
+  }
+
+  int32_t count = 1;
+  nsAutoString nodeNameAttribute;
+  GetNameAttribute(aNode, nodeNameAttribute);
+  for (const Element* e = aNode->GetPreviousElementSibling(); e; e = e->GetPreviousElementSibling()) {
+    nsAutoString elementNamespaceURI;
+    e->GetNamespaceURI(elementNamespaceURI);
+    nsAutoString elementNameAttribute;
+    GetNameAttribute(e, elementNameAttribute);
+    if (e->LocalName().Equals(nodeLocalName) && elementNamespaceURI.Equals(nodeNamespaceURI) &&
+        (nodeNameAttribute.IsEmpty() || elementNameAttribute.Equals(nodeNameAttribute))) {
+      ++count;
+    }
+  }
+
+  nsAutoString namePart;
+  nsAutoString countPart;
+  if (!nodeNameAttribute.IsEmpty()) {
+    nsAutoString quotedArgument;
+    QuoteArgument(nodeNameAttribute, quotedArgument);
+    namePart.Assign(NS_LITERAL_STRING("[@name=") + quotedArgument + NS_LITERAL_STRING("]"));
+  }
+  if (count != 1) {
+    countPart.Assign(NS_LITERAL_STRING("["));
+    countPart.AppendInt(count);
+    countPart.Append(NS_LITERAL_STRING("]"));
+  }
+  Generate(aNode->GetParentNode(), aResult);
+  aResult.Append(NS_LITERAL_STRING("/") + tag + namePart + countPart);
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/XPathGenerator.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef XPATHGENERATOR_H__
+#define XPATHGENERATOR_H__
+#include "nsString.h"
+#include "nsINode.h"
+
+class XPathGenerator
+{
+public:
+  /**
+   * Return a properly quoted string to insert into an XPath
+   * */
+  static void QuoteArgument(const nsAString& aArg, nsAString& aResult);
+
+  /**
+   * Return a valid XPath for the given node (usually the local name itself)
+   * */
+  static void EscapeName(const nsAString& aName, nsAString& aResult);
+
+  /**
+   * Generate an approximate XPath query to an (X)HTML node
+   * */
+  static void Generate(const nsINode* aNode, nsAString& aResult);
+};
+
+#endif
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -123,16 +123,17 @@ EXPORTS += [
     'nsTextFragment.h',
     'nsTraversal.h',
     'nsTreeSanitizer.h',
     'nsViewportInfo.h',
     'nsWindowMemoryReporter.h',
     'nsWrapperCache.h',
     'nsWrapperCacheInlines.h',
     'nsXMLNameSpaceMap.h',
+    'XPathGenerator.h',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
     EXPORTS += [
         'nsDOMDataChannel.h',
         'nsDOMDataChannelDeclarations.h',
     ]
 
@@ -357,16 +358,17 @@ UNIFIED_SOURCES += [
     'TimeoutExecutor.cpp',
     'TimeoutHandler.cpp',
     'TimeoutManager.cpp',
     'TreeWalker.cpp',
     'WebKitCSSMatrix.cpp',
     'WebSocket.cpp',
     'WindowNamedPropertiesHandler.cpp',
     'WindowOrientationObserver.cpp',
+    'XPathGenerator.cpp',
 ]
 
 if CONFIG['MOZ_WEBRTC']:
     UNIFIED_SOURCES += [
         'nsDOMDataChannel.cpp',
     ]
 
 if CONFIG['FUZZING']:
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -103,16 +103,18 @@
 #include "nsGlobalWindow.h"
 #include "nsDOMMutationObserver.h"
 #include "GeometryUtils.h"
 #include "nsIAnimationObserver.h"
 #include "nsChildContentList.h"
 #include "mozilla/dom/NodeBinding.h"
 #include "mozilla/dom/BindingDeclarations.h"
 
+#include "XPathGenerator.h"
+
 #ifdef ACCESSIBILITY
 #include "mozilla/dom/AccessibleNode.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsINode::nsSlots::nsSlots()
@@ -3018,16 +3020,22 @@ nsINode::AddAnimationObserver(nsIAnimati
 void
 nsINode::AddAnimationObserverUnlessExists(
                                nsIAnimationObserver* aAnimationObserver)
 {
   AddMutationObserverUnlessExists(aAnimationObserver);
   OwnerDoc()->SetMayHaveAnimationObservers();
 }
 
+void
+nsINode::GenerateXPath(nsAString& aResult)
+{
+  XPathGenerator::Generate(this, aResult);
+}
+
 bool
 nsINode::IsApzAware() const
 {
   return IsNodeApzAware();
 }
 
 bool
 nsINode::IsNodeApzAwareInternal() const
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -1746,16 +1746,17 @@ protected:
 public:
   // Makes nsINode object to keep aObject alive.
   void BindObject(nsISupports* aObject);
   // After calling UnbindObject nsINode object doesn't keep
   // aObject alive anymore.
   void UnbindObject(nsISupports* aObject);
 
   void GetBoundMutationObservers(nsTArray<RefPtr<nsDOMMutationObserver> >& aResult);
+  void GenerateXPath(nsAString& aResult);
 
   already_AddRefed<mozilla::dom::AccessibleNode> GetAccessibleNode();
 
   /**
    * Returns the length of this node, as specified at
    * <http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node-length>
    */
   uint32_t Length() const;
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -103,16 +103,18 @@ interface Node : EventTarget {
   [Throws, Func="IsChromeOrXBL"]
   any getUserData(DOMString key);
   [ChromeOnly]
   readonly attribute Principal nodePrincipal;
   [ChromeOnly]
   readonly attribute URI? baseURIObject;
   [ChromeOnly]
   sequence<MutationObserver> getBoundMutationObservers();
+  [ChromeOnly]
+  DOMString generateXPath();
 
 #ifdef ACCESSIBILITY
   [Pref="accessibility.AOM.enabled"]
   readonly attribute AccessibleNode? accessibleNode;
 #endif
 };
 
 dictionary GetRootNodeOptions {
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -234,17 +234,16 @@ EXTRA_JS_MODULES += [
     'secondscreen/RokuApp.jsm',
     'secondscreen/SimpleServiceDiscovery.jsm',
     'SelectContentHelper.jsm',
     'SelectParentHelper.jsm',
     'ServiceRequest.jsm',
     'Services.jsm',
     'sessionstore/FormData.jsm',
     'sessionstore/ScrollPosition.jsm',
-    'sessionstore/XPathGenerator.jsm',
     'ShortcutUtils.jsm',
     'Sqlite.jsm',
     'Task.jsm',
     'Timer.jsm',
     'Troubleshoot.jsm',
     'UpdateUtils.jsm',
     'WebChannel.jsm',
     'WindowDraggingUtils.jsm',
--- a/toolkit/modules/sessionstore/FormData.jsm
+++ b/toolkit/modules/sessionstore/FormData.jsm
@@ -6,19 +6,16 @@
 
 this.EXPORTED_SYMBOLS = ["FormData"];
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "XPathGenerator",
-				  "resource://gre/modules/XPathGenerator.jsm");
-
 /**
  * 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";
 }
 
@@ -93,16 +90,66 @@ this.FormData = Object.freeze({
     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 = Components.interfaces.nsIDOMXPathResult.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() {
+    // 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.restorableFormNodesXPath;
+    return (this.restorableFormNodesXPath = formNodesXPath);
+  },
+
   /**
    * Collect form data for a given |frame| *not* including any subframes.
    *
    * The returned object may have an "id", "xpath", or "innerHTML" key or a
    * combination of those three. Form data stored under "id" is for input
    * fields with id attributes. Data stored under "xpath" is used for input
    * fields that don't have a unique id and need to be queried using XPath.
    * The "innerHTML" key is used for editable documents (designMode=on).
@@ -118,19 +165,19 @@ var FormDataInternal = {
    *
    * @param  doc
    *         DOMDocument instance to obtain form data for.
    * @return object
    *         Form data encoded in an object.
    */
   collect({document: doc}) {
     let formNodes = doc.evaluate(
-      XPathGenerator.restorableFormNodes,
+      this.restorableFormNodesXPath,
       doc,
-      XPathGenerator.resolveNS,
+      this.resolveNS.bind(this),
       Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null
     );
 
     let node;
     let ret = {};
 
     // Limit the number of XPath expressions for performance reasons. See
     // bug 477564.
@@ -193,17 +240,17 @@ var FormDataInternal = {
       }
 
       if (node.id) {
         ret.id = ret.id || {};
         ret.id[node.id] = value;
       } else {
         generatedCount++;
         ret.xpath = ret.xpath || {};
-        ret.xpath[XPathGenerator.generate(node)] = value;
+        ret.xpath[node.generateXPath()] = value;
       }
     }
 
     // designMode is undefined e.g. for XUL documents (as about:config)
     if ((doc.designMode || "") == "on" && doc.body) {
       // eslint-disable-next-line no-unsanitized/property
       ret.innerHTML = doc.body.innerHTML;
     }
@@ -251,17 +298,17 @@ var FormDataInternal = {
     }
 
     if ("id" in data) {
       let retrieveNode = id => doc.getElementById(id);
       this.restoreManyInputValues(data.id, retrieveNode);
     }
 
     if ("xpath" in data) {
-      let retrieveNode = xpath => XPathGenerator.resolve(doc, xpath);
+      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.fireEvent(doc.body, "input");
deleted file mode 100644
--- a/toolkit/modules/sessionstore/XPathGenerator.jsm
+++ /dev/null
@@ -1,119 +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";
-
-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);
-  }
-};