Bug 1347176 - Implement label element extraction logic of an input field for filling form.; r?MattN draft
authorSean Lee <selee@mozilla.com>
Fri, 31 Mar 2017 17:43:47 +0800
changeset 562667 581af7535fbd55a143a17db87413d08fa9176d53
parent 561885 1f1c921f172cfb6f299e9ed810b748de6bede180
child 562829 ba1b8a9a060a5df3dda4153d25007f45dce48031
push id54079
push userbmo:selee@mozilla.com
push dateFri, 14 Apr 2017 06:41:20 +0000
reviewersMattN
bugs1347176
milestone55.0a1
Bug 1347176 - Implement label element extraction logic of an input field for filling form.; r?MattN MozReview-Commit-ID: 5uGo1jBBFsC
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/test/unit/test_findLabelElements.js
browser/extensions/formautofill/test/unit/xpcshell.ini
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -28,9 +28,47 @@ this.FormAutofillUtils = {
     if (middleName) {
       fullName += " " + middleName;
     }
     if (lastName) {
       fullName += " " + lastName;
     }
     return fullName;
   },
+
+  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 [];
+  },
 };
+
+this.log = null;
+this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
+
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_findLabelElements.js
@@ -0,0 +1,90 @@
+"use strict";
+
+Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+
+const TESTCASES = [
+  {
+    description: "Input contains in a label element.",
+    document: `<form>
+                 <label id="labelA"> label type A
+                   <input id="typeA" type="text">
+                 </label>
+               </form>`,
+    inputId: "typeA",
+    expectedLabelIds: ["labelA"],
+  },
+  {
+    description: "Input contains in a label element.",
+    document: `<label id="labelB"> label type B
+                 <div> inner div
+                   <input id="typeB" type="text">
+                 </div>
+               </label>`,
+    inputId: "typeB",
+    expectedLabelIds: ["labelB"],
+  },
+  {
+    description: "\"for\" attribute used to indicate input by one label.",
+    document: `<label id="labelC" for="typeC">label type C</label>
+               <input id="typeC" type="text">`,
+    inputId: "typeC",
+    expectedLabelIds: ["labelC"],
+  },
+  {
+    description: "\"for\" attribute used to indicate input by multiple labels.",
+    document: `<form>
+                 <label id="labelD1" for="typeD">label type D1</label>
+                 <label id="labelD2" for="typeD">label type D2</label>
+                 <label id="labelD3" for="typeD">label type D3</label>
+                 <input id="typeD" type="text">
+               </form>`,
+    inputId: "typeD",
+    expectedLabelIds: ["labelD1", "labelD2", "labelD3"],
+  },
+  {
+    description: "\"for\" attribute used to indicate input by multiple labels with space prefix/postfix.",
+    document: `<label id="labelE1" for="typeE">label type E1</label>
+               <label id="labelE2" for="typeE  ">label type E2</label>
+               <label id="labelE3" for="  TYPEe">label type E3</label>
+               <label id="labelE4" for="  typeE  ">label type E4</label>
+               <input id="   typeE  " type="text">`,
+    inputId: "   typeE  ",
+    expectedLabelIds: [],
+  },
+  {
+    description: "Input contains in a label element.",
+    document: `<label id="labelF"> label type F
+                 <label for="dummy"> inner label
+                   <input id="typeF" type="text">
+                   <input id="dummy" type="text">
+                 </div>
+               </label>`,
+    inputId: "typeF",
+    expectedLabelIds: ["labelF"],
+  },
+  {
+    description: "\"for\" attribute used to indicate input by labels out of the form.",
+    document: `<label id="labelG1" for="typeG">label type G1</label>
+               <form>
+                 <label id="labelG2" for="typeG">label type G2</label>
+                 <input id="typeG" type="text">
+               </form>
+               <label id="labelG3" for="typeG">label type G3</label>`,
+    inputId: "typeG",
+    expectedLabelIds: ["labelG1", "labelG2", "labelG3"],
+  },
+];
+
+TESTCASES.forEach(testcase => {
+  add_task(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);
+
+    Assert.deepEqual(labels.map(l => l.id), testcase.expectedLabelIds);
+  });
+});
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 firefox-appdir = browser
 head = head.js
 support-files =
 
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
 [test_enabledStatus.js]
+[test_findLabelElements.js]
 [test_getFormInputDetails.js]
 [test_markAsAutofillField.js]
 [test_profileAutocompleteResult.js]
 [test_profileStorage.js]
 [test_savedFieldNames.js]