Bug 1339721 - Use FormAutofillHandler.autofillFormFields to fill in fields.; r=MattN
authorSean Lee <selee@mozilla.com>
Wed, 15 Feb 2017 16:27:45 +0800
changeset 392059 ec0a9237ed60be50601df0a0dfd33491d0ae24f3
parent 392058 10479537baf6bdc5d7168c586a17067b757b1023
child 392060 0041f17d932f68322f8693a05574f9870299eb12
push id7198
push userjlorenzo@mozilla.com
push dateTue, 18 Apr 2017 12:07:49 +0000
treeherdermozilla-beta@d57aa49c3948 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1339721
milestone54.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 1339721 - Use FormAutofillHandler.autofillFormFields to fill in fields.; r=MattN MozReview-Commit-ID: KgphpgQ4FNp
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -228,30 +228,19 @@ let ProfileAutocomplete = {
 
     if (selectedIndex == -1 ||
         !this._lastAutoCompleteResult ||
         this._lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
       return;
     }
 
     let profile = JSON.parse(this._lastAutoCompleteResult.getCommentAt(selectedIndex));
+    let formHandler = FormAutofillContent.getFormHandler(focusedInput);
 
-    // TODO: FormAutofillHandler.autofillFormFields will be used for filling
-    // fields logic eventually.
-    for (let inputInfo of formDetails) {
-      // Skip filling the value of focused input which is filled in
-      // FormFillController.
-      if (inputInfo.element === focusedInput) {
-        continue;
-      }
-      let value = profile[inputInfo.fieldName];
-      if (value) {
-        inputInfo.element.setUserInput(value);
-      }
-    }
+    formHandler.autofillFormFields(profile, focusedInput);
   },
 };
 
 /**
  * Handles content's interactions for the process.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
@@ -291,28 +280,41 @@ var FormAutofillContent = {
       if (element == detail.element) {
         return detail;
       }
     }
     return null;
   },
 
   /**
+   * Get the form's handler from cache which is created after page identified.
+   *
+   * @param {HTMLInputElement} element Focused input which triggered profile searching
+   * @returns {Array<Object>|null}
+   *          Return target form's handler from content cache
+   *          (or return null if the information is not found in the cache).
+   *
+   */
+  getFormHandler(element) {
+    let rootElement = FormLikeFactory.findRootForField(element);
+    return this._formsDetails.get(rootElement);
+  },
+
+  /**
    * Get the form's information from cache which is created after page identified.
    *
    * @param {HTMLInputElement} element Focused input which triggered profile searching
    * @returns {Array<Object>|null}
-   *          Return target form's information that cloned from content cache
+   *          Return target form's information from content cache
    *          (or return null if the information is not found in the cache).
    *
    */
   getFormDetails(element) {
-    let rootElement = FormLikeFactory.findRootForField(element);
-    let formDetails = this._formsDetails.get(rootElement);
-    return formDetails ? formDetails.fieldDetails : null;
+    let formHandler = this.getFormHandler(element);
+    return formHandler ? formHandler.fieldDetails : null;
   },
 
   getAllFieldNames(element) {
     let formDetails = this.getFormDetails(element);
     return formDetails.map(record => record.fieldName);
   },
 
   identifyAutofillFields(doc) {
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -98,50 +98,35 @@ FormAutofillHandler.prototype = {
 
     log.debug("Collected details on", autofillData.length, "fields");
 
     return autofillData;
   },
 
   /**
    * Processes form fields that can be autofilled, and populates them with the
-   * data provided by backend.
+   * profile provided by backend.
    *
-   * @param {Array<Object>} autofillResult
-   *        Data returned by the user interface.
-   *        [{
-   *          section: Value originally provided to the user interface.
-   *          addressType: Value originally provided to the user interface.
-   *          contactType: Value originally provided to the user interface.
-   *          fieldName: Value originally provided to the user interface.
-   *          value: String with which the field should be updated.
-   *          index: Index to match the input in fieldDetails
-   *        }],
-   *        }
+   * @param {Object} profile
+   *        A profile to be filled in.
+   * @param {Object} focusedInput
+   *        A focused input element which is skipped for filling.
    */
-  autofillFormFields(autofillResult) {
-    log.debug("autofillFormFields:", autofillResult);
-    for (let field of autofillResult) {
-      // TODO: Skip filling the value of focused input which is filled in
-      // FormFillController.
+  autofillFormFields(profile, focusedInput) {
+    log.debug("profile in autofillFormFields:", profile);
+    for (let fieldDetail of this.fieldDetails) {
+      // Avoid filling field value in the following cases:
+      // 1. the focused input which is filled in FormFillController.
+      // 2. a non-empty input field
+      // 3. the invalid value set
 
-      // Get the field details, if it was processed by the user interface.
-      let fieldDetail = this.fieldDetails[field.index];
-
-      // Avoid the invalid value set
-      if (!fieldDetail || !field.value) {
+      if (fieldDetail.element === focusedInput ||
+          fieldDetail.element.value) {
         continue;
       }
 
-      let info = FormAutofillHeuristics.getInfo(fieldDetail.element);
-      if (!info ||
-          field.section != info.section ||
-          field.addressType != info.addressType ||
-          field.contactType != info.contactType ||
-          field.fieldName != info.fieldName) {
-        Cu.reportError("Autocomplete tokens mismatched");
-        continue;
+      let value = profile[fieldDetail.fieldName];
+      if (value) {
+        fieldDetail.element.setUserInput(value);
       }
-
-      fieldDetail.element.setUserInput(field.value);
     }
   },
 };
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -8,17 +8,17 @@ Cu.import("resource://formautofill/FormA
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><input id="country">
                <input id='email'><input id="tel"></form>`,
     fieldDetails: [],
-    profileData: [],
+    profileData: {},
     expectedResult: {
       "street-addr": "",
       "city": "",
       "country": "",
       "email": "",
       "tel": "",
     },
   },
@@ -35,25 +35,23 @@ const TESTCASES = [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
-      {"section": "", "addressType": "", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
-    ],
+    profileData: {
+      "street-address": "2 Harrison St",
+      "address-level2": "San Francisco",
+      "country": "US",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
     expectedResult: {
       "street-addr": "2 Harrison St",
       "city": "San Francisco",
       "country": "US",
       "email": "foo@mozilla.com",
       "tel": "1234567",
     },
   },
@@ -70,25 +68,23 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
-      {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
-    ],
+    profileData: {
+      "street-address": "2 Harrison St",
+      "address-level2": "San Francisco",
+      "country": "US",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
     expectedResult: {
       "street-addr": "2 Harrison St",
       "city": "San Francisco",
       "country": "US",
       "email": "foo@mozilla.com",
       "tel": "1234567",
     },
   },
@@ -105,25 +101,23 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5},
-      {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6},
-    ],
+    profileData: {
+      "street-address": "2 Harrison St",
+      "address-level2": "San Francisco",
+      "country": "US",
+      "email": "",
+      "tel": "",
+    },
     expectedResult: {
       "street-addr": "2 Harrison St",
       "city": "San Francisco",
       "country": "US",
       "email": "",
       "tel": "",
     },
   },
@@ -140,25 +134,23 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email", "element": {}},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel", "element": {}},
     ],
-    profileData: [
-      {"section": "", "addressType": "shipping", "fieldName": "given-name", "contactType": "", "index": 0, "value": "foo"},
-      {"section": "", "addressType": "shipping", "fieldName": "family-name", "contactType": "", "index": 1, "value": "bar"},
-      {"section": "", "addressType": "shipping", "fieldName": "street-address", "contactType": "", "index": 2, "value": "2 Harrison St"},
-      {"section": "", "addressType": "shipping", "fieldName": "address-level2", "contactType": "", "index": 3, "value": "San Francisco"},
-      {"section": "", "addressType": "shipping", "fieldName": "country", "contactType": "", "index": 4, "value": "US"},
-      {"section": "", "addressType": "shipping", "fieldName": "email", "contactType": "", "index": 5, "value": "foo@mozilla.com"},
-      {"section": "", "addressType": "shipping", "fieldName": "tel", "contactType": "", "index": 6, "value": "1234567"},
-    ],
+    profileData: {
+      "street-address": "",
+      "address-level2": "",
+      "country": "",
+      "email": "foo@mozilla.com",
+      "tel": "1234567",
+    },
     expectedResult: {
       "street-addr": "",
       "city": "",
       "country": "",
       "email": "foo@mozilla.com",
       "tel": "1234567",
     },
   },
@@ -169,22 +161,34 @@ for (let tc of TESTCASES) {
     let testcase = tc;
     add_task(function* () {
       do_print("Starting testcase: " + testcase.description);
 
       let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                 testcase.document);
       let form = doc.querySelector("form");
       let handler = new FormAutofillHandler(form);
+      let onChangePromises = [];
 
       handler.fieldDetails = testcase.fieldDetails;
       handler.fieldDetails.forEach((field, index) => {
-        field.element = doc.querySelectorAll("input")[index];
+        let element = doc.querySelectorAll("input")[index];
+        field.element = element;
+        if (!testcase.profileData[field.fieldName]) {
+          // Avoid waiting for `change` event of a input with a blank value to
+          // be filled.
+          return;
+        }
+        onChangePromises.push(new Promise(resolve => {
+          element.addEventListener("change", () => {
+            let id = element.id;
+            Assert.equal(element.value, testcase.expectedResult[id],
+                        "Check the " + id + " fields were filled with correct data");
+            resolve();
+          }, {once: true});
+        }));
       });
 
       handler.autofillFormFields(testcase.profileData);
-      for (let id in testcase.expectedResult) {
-        Assert.equal(doc.getElementById(id).value, testcase.expectedResult[id],
-                    "Check the " + id + " fields were filled with correct data");
-      }
+      yield Promise.all(onChangePromises);
     });
   })();
 }