Bug 1362330 - Move XPath generation to Node's interface and move all remaining XPathGenerator.jsm functions to FormData. r=smaug, r=mikedeboer
☠☠ backed out by 8962204c2d58 ☠ ☠
authorBeekill95 <nnn_bikiu0707@yahoo.com>
Thu, 15 Jun 2017 08:49:17 +0700
changeset 364658 a4230d2eab5f36d3d29fcf1da405763da285733b
parent 364657 0318ea16877d756393d17045ea03d063ed8efd22
child 364659 6660b55684d3925aa6fdbe6fc001df7c4cd72152
push id91589
push userryanvm@gmail.com
push dateMon, 19 Jun 2017 14:08:32 +0000
treeherdermozilla-inbound@6660b55684d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, mikedeboer
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 - Move XPath generation to Node's interface and move all remaining XPathGenerator.jsm functions to FormData. r=smaug, r=mikedeboer 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
@@ -122,16 +122,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',
     ]
 
@@ -355,16 +356,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
@@ -102,16 +102,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()
@@ -3012,16 +3014,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
@@ -1754,16 +1754,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
@@ -102,16 +102,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) {
       ret.innerHTML = doc.body.innerHTML;
     }
 
@@ -250,17 +297,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") {
         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);
-  }
-};