Bug 1390433 - (From 1387988)[Form Autofill] Move "findLabelElements" function to FormAutofillHeuristics.jsm. r=MattN draft
authorLuke Chang <lchang@mozilla.com>
Mon, 07 Aug 2017 18:34:27 +0800
changeset 648832 49ae18b8d224c7cf0b417695f1116439e81bb252
parent 648831 f9ab71432c920ef8842746a143b3a457083e6f80
child 648833 737d420bf61dda43f405434086515216e628586b
child 649647 597ca8c2c0f98f0041f0d01a47ddf666f4911844
child 649751 2b0895148593f43b1773d2a1e4d69327b5efd3a4
push id74896
push userschung@mozilla.com
push dateFri, 18 Aug 2017 10:48:05 +0000
reviewersMattN
bugs1390433, 1387988
milestone56.0
Bug 1390433 - (From 1387988)[Form Autofill] Move "findLabelElements" function to FormAutofillHeuristics.jsm. r=MattN MozReview-Commit-ID: 93c9R7JaCLA
browser/extensions/formautofill/FormAutofillHeuristics.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
browser/extensions/formautofill/test/unit/test_findLabelElements.js
tools/lint/eslint/modules.json
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
  * Form Autofill field heuristics.
  */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["FormAutofillHeuristics"];
+this.EXPORTED_SYMBOLS = ["FormAutofillHeuristics", "LabelUtils"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
 this.log = null;
@@ -177,16 +177,94 @@ class FieldScanner {
     return this.fieldDetails.filter(f => f.fieldName && !f._duplicated);
   }
 
   elementExisting(index) {
     return index < this._elements.length;
   }
 }
 
+this.LabelUtils = {
+  // The tag name list is from Chromium except for "STYLE":
+  // eslint-disable-next-line max-len
+  // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e
+  EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"],
+  /**
+   * Extract all strings of an element's children to an array.
+   * "element.textContent" is a string which is merged of all children nodes,
+   * and this function provides an array of the strings contains in an element.
+   *
+   * @param  {Object} element
+   *         A DOM element to be extracted.
+   * @returns {Array}
+   *          All strings in an element.
+   */
+  extractLabelStrings(element) {
+    let strings = [];
+    let _extractLabelStrings = (el) => {
+      if (this.EXCLUDED_TAGS.includes(el.tagName)) {
+        return;
+      }
+
+      if (el.nodeType == Ci.nsIDOMNode.TEXT_NODE ||
+          el.childNodes.length == 0) {
+        let trimmedText = el.textContent.trim();
+        if (trimmedText) {
+          strings.push(trimmedText);
+        }
+        return;
+      }
+
+      for (let node of el.childNodes) {
+        if (node.nodeType != Ci.nsIDOMNode.ELEMENT_NODE &&
+            node.nodeType != Ci.nsIDOMNode.TEXT_NODE) {
+          continue;
+        }
+        _extractLabelStrings(node);
+      }
+    };
+    _extractLabelStrings(element);
+    return strings;
+  },
+
+  findLabelElements(element) {
+    let document = element.ownerDocument;
+    let labels = [];
+    // TODO: querySelectorAll is inefficient here. However, bug 1339726 is for
+    // a more efficient implementation from DOM API perspective. This function
+    // should be refined after input.labels API landed.
+    for (let label of document.querySelectorAll("label[for]")) {
+      if (element.id == label.htmlFor) {
+        labels.push(label);
+      }
+    }
+
+    if (labels.length > 0) {
+      log.debug("Label found by ID", element.id);
+      return labels;
+    }
+
+    let parent = element.parentNode;
+    if (!parent) {
+      return [];
+    }
+    do {
+      if (parent.tagName == "LABEL" &&
+          parent.control == element &&
+          !parent.hasAttribute("for")) {
+        log.debug("Label found in input's parent or ancestor.");
+        return [parent];
+      }
+      parent = parent.parentNode;
+    } while (parent);
+
+    return [];
+  },
+};
+
 /**
  * Returns the autocomplete information of fields according to heuristics.
  */
 this.FormAutofillHeuristics = {
   RULES: null,
 
   /**
    * Try to match the telephone related fields to the grammar
@@ -365,19 +443,19 @@ this.FormAutofillHeuristics = {
 
     let labelStrings;
     let getElementStrings = {};
     getElementStrings[Symbol.iterator] = function* () {
       yield element.id;
       yield element.name;
       if (!labelStrings) {
         labelStrings = [];
-        let labels = FormAutofillUtils.findLabelElements(element);
+        let labels = LabelUtils.findLabelElements(element);
         for (let label of labels) {
-          labelStrings.push(...FormAutofillUtils.extractLabelStrings(label));
+          labelStrings.push(...LabelUtils.extractLabelStrings(label));
         }
       }
       yield *labelStrings;
     };
 
     for (let regexp of regexps) {
       for (let string of getElementStrings) {
         // The original regexp "(?<!united )state|county|region|province" for
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -123,92 +123,16 @@ this.FormAutofillUtils = {
       }
     } else if (!(element instanceof Ci.nsIDOMHTMLSelectElement)) {
       return false;
     }
 
     return true;
   },
 
-  // The tag name list is from Chromium except for "STYLE":
-  // eslint-disable-next-line max-len
-  // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e
-  EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"],
-  /**
-   * Extract all strings of an element's children to an array.
-   * "element.textContent" is a string which is merged of all children nodes,
-   * and this function provides an array of the strings contains in an element.
-   *
-   * @param  {Object} element
-   *         A DOM element to be extracted.
-   * @returns {Array}
-   *          All strings in an element.
-   */
-  extractLabelStrings(element) {
-    let strings = [];
-    let _extractLabelStrings = (el) => {
-      if (this.EXCLUDED_TAGS.includes(el.tagName)) {
-        return;
-      }
-
-      if (el.nodeType == Ci.nsIDOMNode.TEXT_NODE ||
-          el.childNodes.length == 0) {
-        let trimmedText = el.textContent.trim();
-        if (trimmedText) {
-          strings.push(trimmedText);
-        }
-        return;
-      }
-
-      for (let node of el.childNodes) {
-        if (node.nodeType != Ci.nsIDOMNode.ELEMENT_NODE &&
-            node.nodeType != Ci.nsIDOMNode.TEXT_NODE) {
-          continue;
-        }
-        _extractLabelStrings(node);
-      }
-    };
-    _extractLabelStrings(element);
-    return strings;
-  },
-
-  findLabelElements(element) {
-    let document = element.ownerDocument;
-    let labels = [];
-    // TODO: querySelectorAll is inefficient here. However, bug 1339726 is for
-    // a more efficient implementation from DOM API perspective. This function
-    // should be refined after input.labels API landed.
-    for (let label of document.querySelectorAll("label[for]")) {
-      if (element.id == label.htmlFor) {
-        labels.push(label);
-      }
-    }
-
-    if (labels.length > 0) {
-      log.debug("Label found by ID", element.id);
-      return labels;
-    }
-
-    let parent = element.parentNode;
-    if (!parent) {
-      return [];
-    }
-    do {
-      if (parent.tagName == "LABEL" &&
-          parent.control == element &&
-          !parent.hasAttribute("for")) {
-        log.debug("Label found in input's parent or ancestor.");
-        return [parent];
-      }
-      parent = parent.parentNode;
-    } while (parent);
-
-    return [];
-  },
-
   loadDataFromScript(url, sandbox = {}) {
     let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                          .getService(Ci.mozIJSSubScriptLoader);
     scriptLoader.loadSubScript(url, sandbox, "utf-8");
     return sandbox;
   },
 
   /**
--- a/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
+++ b/browser/extensions/formautofill/test/unit/test_extractLabelStrings.js
@@ -1,11 +1,11 @@
 "use strict";
 
-Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
 
 const TESTCASES = [
   {
     description: "A label element contains one input element.",
     document: `<label id="typeA"> label type A
                  <!-- This comment should not be extracted. -->
                  <input type="text">
                  <script>FOO</script>
@@ -54,13 +54,13 @@ const TESTCASES = [
 TESTCASES.forEach(testcase => {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
 
     let doc = MockDocument.createTestDocument(
       "http://localhost:8080/test/", testcase.document);
 
     let element = doc.getElementById(testcase.inputId);
-    let strings = FormAutofillUtils.extractLabelStrings(element);
+    let strings = LabelUtils.extractLabelStrings(element);
 
     Assert.deepEqual(strings, testcase.expectedStrings);
   });
 });
--- a/browser/extensions/formautofill/test/unit/test_findLabelElements.js
+++ b/browser/extensions/formautofill/test/unit/test_findLabelElements.js
@@ -1,11 +1,11 @@
 "use strict";
 
-Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+Cu.import("resource://formautofill/FormAutofillHeuristics.jsm");
 
 const TESTCASES = [
   {
     description: "Input contains in a label element.",
     document: `<form>
                  <label id="labelA"> label type A
                    <input id="typeA" type="text">
                  </label>
@@ -78,13 +78,13 @@ const TESTCASES = [
 TESTCASES.forEach(testcase => {
   add_task(async function() {
     do_print("Starting testcase: " + testcase.description);
 
     let doc = MockDocument.createTestDocument(
       "http://localhost:8080/test/", testcase.document);
 
     let input = doc.getElementById(testcase.inputId);
-    let labels = FormAutofillUtils.findLabelElements(input);
+    let labels = LabelUtils.findLabelElements(input);
 
     Assert.deepEqual(labels.map(l => l.id), testcase.expectedLabelIds);
   });
 });
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -72,16 +72,17 @@
   "fakeservices.js": ["FakeCryptoService", "FakeFilesystemService", "FakeGUIDService", "fakeSHA256HMAC"],
   "file_expandosharing.jsm": ["checkFromJSM"],
   "file_stringencoding.jsm": ["checkFromJSM"],
   "file_url.jsm": ["checkFromJSM"],
   "file_worker_url.jsm": ["checkFromJSM"],
   "Finder.jsm": ["Finder", "GetClipboardSearchString"],
   "forms.js": ["FormEngine", "FormRec", "FormValidator"],
   "forms.jsm": ["FormData"],
+  "FormAutofillHeuristics.jsm": ["FormAutofillHeuristics", "LabelUtils"],
   "FormAutofillSync.jsm": ["AddressesEngine", "CreditCardsEngine"],
   "frame.js": ["Collector", "Runner", "events", "runTestFile", "log", "timers", "persisted", "shutdownApplication"],
   "FrameScriptManager.jsm": ["getNewLoaderID"],
   "fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
   "fxaccounts.jsm": ["Authentication"],
   "FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
   "FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_FXA_UPDATE_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"],
   "FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"],