Bug 1379588 - Part 1: Add address/creditCard object in handler and refactor collectFormFields for less cross-module call. r=lchang,seanlee
authorsteveck-chung <schung@mozilla.com>
Mon, 24 Jul 2017 12:15:24 +0800
changeset 420687 02fd97e1217ba21a2711cc9bb319b856ca974ee0
parent 420686 ef73974af658a8ccdb723a0ef151c609534539cc
child 420688 4c921604f3926453f1082fc0f780a40ae6cfc4d9
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslchang, seanlee
bugs1379588
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 1379588 - Part 1: Add address/creditCard object in handler and refactor collectFormFields for less cross-module call. r=lchang,seanlee MozReview-Commit-ID: 6FkfGH8nre1
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_collectFormFields.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -90,17 +90,17 @@ AutofillProfileAutoCompleteSearch.protot
    */
   startSearch(searchString, searchParam, previousResult, listener) {
     this.log.debug("startSearch: for", searchString, "with input", formFillController.focusedInput);
     let focusedInput = formFillController.focusedInput;
     this.forceStop = false;
     let info = FormAutofillContent.getInputDetails(focusedInput);
 
     if (!FormAutofillContent.savedFieldNames.has(info.fieldName) ||
-        FormAutofillContent.getFormHandler(focusedInput).filledProfileGUID) {
+        FormAutofillContent.getFormHandler(focusedInput).address.filledRecordGUID) {
       let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
                           .createInstance(Ci.nsIAutoCompleteSearch);
       formHistory.startSearch(searchString, searchParam, previousResult, {
         onSearchResult: (search, result) => {
           listener.onSearchResult(this, result);
           ProfileAutocomplete.setProfileAutoCompleteResult(result);
         },
       });
@@ -380,17 +380,17 @@ var FormAutofillContent = {
     let pendingAddress = handler.createProfile();
     if (Object.keys(pendingAddress).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) {
       this.log.debug(`Not saving since there are only ${Object.keys(pendingAddress).length} usable fields`);
       return true;
     }
 
     this._onFormSubmit({
       address: {
-        guid: handler.filledProfileGUID,
+        guid: handler.address.filledRecordGUID,
         record: pendingAddress,
       },
       // creditCard: {}
     }, domWin);
 
     return true;
   },
 
@@ -479,38 +479,24 @@ var FormAutofillContent = {
     if (!formHandler) {
       let formLike = FormLikeFactory.createFromField(element);
       formHandler = new FormAutofillHandler(formLike);
     } else if (!formHandler.isFormChangedSinceLastCollection) {
       this.log.debug("No control is removed or inserted since last collection.");
       return;
     }
 
-    formHandler.collectFormFields();
+    let validDetails = formHandler.collectFormFields();
 
     this._formsDetails.set(formHandler.form.rootElement, formHandler);
     this.log.debug("Adding form handler to _formsDetails:", formHandler);
 
-    if (formHandler.isValidAddressForm) {
-      formHandler.addressFieldDetails.forEach(
-        detail => this._markAsAutofillField(detail.elementWeakRef.get())
-      );
-    } else {
-      this.log.debug("Ignoring address related fields since it has only",
-                     formHandler.addressFieldDetails.length,
-                     "field(s)");
-    }
-
-    if (formHandler.isValidCreditCardForm) {
-      formHandler.creditCardFieldDetails.forEach(
-        detail => this._markAsAutofillField(detail.elementWeakRef.get())
-      );
-    } else {
-      this.log.debug("Ignoring credit card related fields since it's without credit card number field");
-    }
+    validDetails.forEach(detail =>
+      this._markAsAutofillField(detail.elementWeakRef.get())
+    );
   },
 
   _markAsAutofillField(field) {
     // Since Form Autofill popup is only for input element, any non-Input
     // element should be excluded here.
     if (!field || !(field instanceof Ci.nsIDOMHTMLInputElement)) {
       return;
     }
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -26,16 +26,38 @@ FormAutofillUtils.defineLazyLogGetter(th
  * Handles profile autofill for a DOM Form element.
  * @param {FormLike} form Form that need to be auto filled
  */
 function FormAutofillHandler(form) {
   this.form = form;
   this.fieldDetails = [];
   this.winUtils = this.form.rootElement.ownerGlobal.QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIDOMWindowUtils);
+
+  this.address = {
+    /**
+     * Similar to the `fieldDetails` above but contains address fields only.
+     */
+    fieldDetails: [],
+    /**
+     * String of the filled address' guid.
+     */
+    filledRecordGUID: null,
+  };
+
+  this.creditCard = {
+    /**
+     * Similar to the `fieldDetails` above but contains credit card fields only.
+     */
+    fieldDetails: [],
+    /**
+     * String of the filled creditCard's guid.
+     */
+    filledRecordGUID: null,
+  };
 }
 
 FormAutofillHandler.prototype = {
   /**
    * DOM Form element to which this object is attached.
    */
   form: null,
 
@@ -52,39 +74,24 @@ FormAutofillHandler.prototype = {
    * the same exact combination of these values.
    *
    * A direct reference to the associated element cannot be sent to the user
    * interface because processing may be done in the parent process.
    */
   fieldDetails: null,
 
   /**
-   * Similiar to `fieldDetails`, and `addressFieldDetails` contains the address
-   * records only.
+   * Subcategory of handler that contains address related data.
    */
-  addressFieldDetails: null,
+  address: null,
 
   /**
-   * Similiar to `fieldDetails`, and `creditCardFieldDetails` contains the
-   * Credit Card records only.
+   * Subcategory of handler that contains credit card related data.
    */
-  creditCardFieldDetails: null,
-
-  get isValidAddressForm() {
-    return this.addressFieldDetails.length >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
-  },
-
-  get isValidCreditCardForm() {
-    return this.creditCardFieldDetails.some(i => i.fieldName == "cc-number");
-  },
-
-  /**
-   * String of the filled profile's guid.
-   */
-  filledProfileGUID: null,
+  creditCard: null,
 
   /**
    * A WindowUtils reference of which Window the form belongs
    */
   winUtils: null,
 
   /**
    * Enum for form autofill MANUALLY_MANAGED_STATES values
@@ -103,30 +110,47 @@ FormAutofillHandler.prototype = {
     // can be recognized as there is no element changed. However, we should
     // improve the function to detect the element changes. e.g. a tel field
     // is changed from type="hidden" to type="tel".
     return this._formFieldCount != this.form.elements.length;
   },
 
   /**
    * Set fieldDetails from the form about fields that can be autofilled.
+
+   * @returns {Array} The valid address and credit card details.
    */
   collectFormFields() {
     this._cacheValue.allFieldNames = null;
     this._formFieldCount = this.form.elements.length;
     let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form);
     this.fieldDetails = fieldDetails ? fieldDetails : [];
     log.debug("Collected details on", this.fieldDetails.length, "fields");
 
-    this.addressFieldDetails = this.fieldDetails.filter(
+    this.address.fieldDetails = this.fieldDetails.filter(
       detail => FormAutofillUtils.isAddressField(detail.fieldName)
     );
-    this.creditCardFieldDetails = this.fieldDetails.filter(
+    this.creditCard.fieldDetails = this.fieldDetails.filter(
       detail => FormAutofillUtils.isCreditCardField(detail.fieldName)
     );
+
+    if (this.address.fieldDetails.length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) {
+      log.debug("Ignoring address related fields since it has only",
+                this.address.fieldDetails.length,
+                "field(s)");
+      this.address.fieldDetails = [];
+    }
+
+    if (!this.creditCard.fieldDetails.some(i => i.fieldName == "cc-number")) {
+      log.debug("Ignoring credit card related fields since it's without credit card number field");
+      this.creditCard.fieldDetails = [];
+    }
+
+    return Array.of(...(this.address.fieldDetails),
+                    ...(this.creditCard.fieldDetails));
   },
 
   getFieldDetailByName(fieldName) {
     return this.fieldDetails.find(detail => detail.fieldName == fieldName);
   },
 
   _cacheValue: {
     allFieldNames: null,
@@ -188,18 +212,18 @@ FormAutofillHandler.prototype = {
    * @param {Object} profile
    *        A profile to be filled in.
    * @param {Object} focusedInput
    *        A focused input element which is skipped for filling.
    */
   autofillFormFields(profile, focusedInput) {
     log.debug("profile in autofillFormFields:", profile);
 
-    this.filledProfileGUID = profile.guid;
-    for (let fieldDetail of this.addressFieldDetails) {
+    this.address.filledRecordGUID = profile.guid;
+    for (let fieldDetail of this.address.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
       // 4. value already chosen in select element
 
       let element = fieldDetail.elementWeakRef.get();
       if (!element) {
@@ -250,17 +274,17 @@ FormAutofillHandler.prototype = {
     log.debug("register change handler for filled form:", this.form);
     const onChangeHandler = e => {
       let hasFilledFields;
 
       if (!e.isTrusted) {
         return;
       }
 
-      for (let fieldDetail of this.addressFieldDetails) {
+      for (let fieldDetail of this.address.fieldDetails) {
         let element = fieldDetail.elementWeakRef.get();
 
         if (!element) {
           return;
         }
 
         if (e.target == element || (e.target == element.form && e.type == "reset")) {
           this.changeFieldState(fieldDetail, "NORMAL");
@@ -268,34 +292,34 @@ FormAutofillHandler.prototype = {
 
         hasFilledFields |= (fieldDetail.state == "AUTO_FILLED");
       }
 
       // Unregister listeners and clear guid once no field is in AUTO_FILLED state.
       if (!hasFilledFields) {
         this.form.rootElement.removeEventListener("input", onChangeHandler);
         this.form.rootElement.removeEventListener("reset", onChangeHandler);
-        this.filledProfileGUID = null;
+        this.address.filledRecordGUID = null;
       }
     };
 
     this.form.rootElement.addEventListener("input", onChangeHandler);
     this.form.rootElement.addEventListener("reset", onChangeHandler);
   },
 
   /**
    * Populates result to the preview layers with given profile.
    *
    * @param {Object} profile
    *        A profile to be previewed with
    */
   previewFormFields(profile) {
     log.debug("preview profile in autofillFormFields:", profile);
 
-    for (let fieldDetail of this.addressFieldDetails) {
+    for (let fieldDetail of this.address.fieldDetails) {
       let element = fieldDetail.elementWeakRef.get();
       let value = profile[fieldDetail.fieldName] || "";
 
       // Skip the field that is null
       if (!element) {
         continue;
       }
 
@@ -317,17 +341,17 @@ FormAutofillHandler.prototype = {
   },
 
   /**
    * Clear preview text and background highlight of all fields.
    */
   clearPreviewedFormFields() {
     log.debug("clear previewed fields in:", this.form);
 
-    for (let fieldDetail of this.addressFieldDetails) {
+    for (let fieldDetail of this.address.fieldDetails) {
       let element = fieldDetail.elementWeakRef.get();
       if (!element) {
         log.warn(fieldDetail.fieldName, "is unreachable");
         continue;
       }
 
       element.previewValue = "";
 
@@ -382,17 +406,17 @@ FormAutofillHandler.prototype = {
    * Return the profile that is converted from fieldDetails and only non-empty fields
    * are included.
    *
    * @returns {Object} The new profile that convert from details with trimmed result.
    */
   createProfile() {
     let profile = {};
 
-    this.addressFieldDetails.forEach(detail => {
+    this.address.fieldDetails.forEach(detail => {
       let element = detail.elementWeakRef.get();
       // Remove the unnecessary spaces
       let value = element && element.value.trim();
       if (!value) {
         return;
       }
 
       profile[detail.fieldName] = value;
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -318,31 +318,31 @@ function do_test(testcases, testFn) {
 
         let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                   testcase.document);
         let form = doc.querySelector("form");
         let formLike = FormLikeFactory.createFromForm(form);
         let handler = new FormAutofillHandler(formLike);
         let promises = [];
 
-        handler.addressFieldDetails = testcase.addressFieldDetails;
-        handler.addressFieldDetails.forEach((field, index) => {
+        handler.address.fieldDetails = testcase.addressFieldDetails;
+        handler.address.fieldDetails.forEach((field, index) => {
           let element = doc.querySelectorAll("input, select")[index];
           field.elementWeakRef = Cu.getWeakReference(element);
           if (!testcase.profileData[field.fieldName]) {
             // Avoid waiting for `change` event of a input with a blank value to
             // be filled.
             return;
           }
           promises.push(...testFn(testcase, element));
         });
 
         handler.autofillFormFields(testcase.profileData);
-        Assert.equal(handler.filledProfileGUID, testcase.profileData.guid,
-                     "Check if filledProfileGUID is set correctly");
+        Assert.equal(handler.address.filledRecordGUID, testcase.profileData.guid,
+                     "Check if filledRecordGUID is set correctly");
         await Promise.all(promises);
       });
     })();
   }
 }
 
 do_test(TESTCASES, (testcase, element) => {
   let id = element.id;
--- a/browser/extensions/formautofill/test/unit/test_collectFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_collectFormFields.js
@@ -17,20 +17,25 @@ const TESTCASES = [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
     ],
     creditCardFieldDetails: [],
-    isValidForm: {
-      address: true,
-      creditCard: false,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-line1"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+    ],
     ids: ["given-name", "family-name", "street-addr", "city", "country", "email", "phone"],
   },
   {
     description: "An address and credit card form with autocomplete properties and 1 token",
     document: `<form>
                <input id="given-name" autocomplete="given-name">
                <input id="family-name" autocomplete="family-name">
                <input id="street-addr" autocomplete="street-address">
@@ -53,20 +58,29 @@ const TESTCASES = [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
     ],
     creditCardFieldDetails: [
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
     ],
-    isValidForm: {
-      address: true,
-      creditCard: true,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-number"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
+    ],
   },
   {
     description: "An address form with autocomplete properties and 2 tokens",
     document: `<form><input id="given-name" autocomplete="shipping given-name">
                <input id="family-name" autocomplete="shipping family-name">
                <input id="street-addr" autocomplete="shipping street-address">
                <input id="city" autocomplete="shipping address-level2">
                <input id="country" autocomplete="shipping country">
@@ -77,20 +91,25 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
     ],
     creditCardFieldDetails: [],
-    isValidForm: {
-      address: true,
-      creditCard: false,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+    ],
   },
   {
     description: "Form with autocomplete properties and profile is partly matched",
     document: `<form><input id="given-name" autocomplete="shipping given-name">
                <input id="family-name" autocomplete="shipping family-name">
                <input id="street-addr" autocomplete="shipping street-address">
                <input autocomplete="shipping address-level2">
                <select autocomplete="shipping country"></select>
@@ -101,20 +120,25 @@ const TESTCASES = [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
     ],
     creditCardFieldDetails: [],
-    isValidForm: {
-      address: true,
-      creditCard: false,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "country"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+    ],
   },
   {
     description: "It's a valid address and credit card form.",
     document: `<form>
                <input id="given-name" autocomplete="shipping given-name">
                <input id="family-name" autocomplete="shipping family-name">
                <input id="street-addr" autocomplete="shipping street-address">
                <input id="cc-number" autocomplete="shipping cc-number">
@@ -122,43 +146,35 @@ const TESTCASES = [
     addressFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
     ],
     creditCardFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "cc-number"},
     ],
-    isValidForm: {
-      address: true,
-      creditCard: true,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "cc-number"},
+    ],
   },
   {
     description: "It's an invalid address and credit form.",
     document: `<form>
                <input id="given-name" autocomplete="shipping given-name">
                <input autocomplete="shipping address-level2">
                <input id="cc-name" autocomplete="cc-name">
                <input id="cc-exp-month" autocomplete="cc-exp-month">
                <input id="cc-exp-year" autocomplete="cc-exp-year">
                </form>`,
-    addressFieldDetails: [
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
-      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "address-level2"},
-    ],
-    creditCardFieldDetails: [
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-name"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-month"},
-      {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-exp-year"},
-    ],
-    isValidForm: {
-      address: false,
-      creditCard: false,
-    },
+    addressFieldDetails: [],
+    creditCardFieldDetails: [],
+    validFieldDetails: [],
   },
   {
     description: "Three sets of adjacent phone number fields",
     document: `<form>
                  <input id="shippingAreaCode" autocomplete="shipping tel" maxlength="3">
                  <input id="shippingPrefix" autocomplete="shipping tel" maxlength="3">
                  <input id="shippingSuffix" autocomplete="shipping tel" maxlength="4">
                  <input id="shippingTelExt" autocomplete="shipping tel-extension">
@@ -181,20 +197,29 @@ const TESTCASES = [
       {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-suffix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
     ],
     creditCardFieldDetails: [],
-    isValidForm: {
-      address: true,
-      creditCard: false,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-extension"},
+      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "billing", "contactType": "", "fieldName": "tel-local-suffix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-country-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel-local-suffix"},
+    ],
     ids: [
       "shippingAreaCode", "shippingPrefix", "shippingSuffix", "shippingTelExt",
       "billingAreaCode", "billingPrefix", "billingSuffix",
       "otherCountryCode", "otherAreaCode", "otherPrefix", "otherSuffix",
     ],
   },
   {
     description: "Dedup the same field names of the different telephone fields.",
@@ -211,20 +236,23 @@ const TESTCASES = [
     addressFieldDetails: [
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
       {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
     ],
     creditCardFieldDetails: [],
-    isValidForm: {
-      address: true,
-      creditCard: false,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+    ],
     ids: ["i1", "i2", "i3", "i4", "homePhone"],
   },
   {
     description: "The duplicated phones of a single one and a set with ac, prefix, suffix.",
     document: `<form>
                  <input id="i1" autocomplete="shipping given-name">
                  <input id="i2" autocomplete="shipping family-name">
                  <input id="i3" autocomplete="shipping street-address">
@@ -244,63 +272,79 @@ const TESTCASES = [
       // this case. We can see if there is any better solution later.
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
 
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
       {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
     ],
     creditCardFieldDetails: [],
-    isValidForm: {
-      address: true,
-      creditCard: false,
-    },
+    validFieldDetails: [
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "given-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "family-name"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "street-address"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "email"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-area-code"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-prefix"},
+      {"section": "", "addressType": "shipping", "contactType": "", "fieldName": "tel-local-suffix"},
+    ],
     ids: ["i1", "i2", "i3", "i4", "singlePhone",
       "shippingAreaCode", "shippingPrefix", "shippingSuffix"],
   },
 ];
 
 for (let tc of TESTCASES) {
   (function() {
     let testcase = tc;
     add_task(async function() {
       do_print("Starting testcase: " + testcase.description);
 
       let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                 testcase.document);
       let form = doc.querySelector("form");
       let formLike = FormLikeFactory.createFromForm(form);
 
-      Array.of(
-        ...testcase.addressFieldDetails,
-        ...testcase.creditCardFieldDetails
-      ).forEach((detail, index) => {
-        let elementRef;
-        if (testcase.ids && testcase.ids[index]) {
-          elementRef = doc.getElementById(testcase.ids[index]);
-        } else {
-          elementRef = doc.querySelector("*[autocomplete*='" + detail.fieldName + "']");
+      function setElementWeakRef(details) {
+        if (!details) {
+          return;
         }
-        detail.elementWeakRef = Cu.getWeakReference(elementRef);
-      });
-      let handler = new FormAutofillHandler(formLike);
 
-      handler.collectFormFields();
+        details.forEach((detail, index) => {
+          let elementRef;
+          if (testcase.ids && testcase.ids[index]) {
+            elementRef = doc.getElementById(testcase.ids[index]);
+          } else {
+            elementRef = doc.querySelector("*[autocomplete*='" + detail.fieldName + "']");
+          }
+          detail.elementWeakRef = Cu.getWeakReference(elementRef);
+        });
+      }
 
       function verifyDetails(handlerDetails, testCaseDetails) {
+        if (handlerDetails === null) {
+          Assert.equal(handlerDetails, testCaseDetails);
+          return;
+        }
         Assert.equal(handlerDetails.length, testCaseDetails.length);
         handlerDetails.forEach((detail, index) => {
           Assert.equal(detail.fieldName, testCaseDetails[index].fieldName, "fieldName");
           Assert.equal(detail.section, testCaseDetails[index].section, "section");
           Assert.equal(detail.addressType, testCaseDetails[index].addressType, "addressType");
           Assert.equal(detail.contactType, testCaseDetails[index].contactType, "contactType");
           Assert.equal(detail.elementWeakRef.get(), testCaseDetails[index].elementWeakRef.get(), "DOM reference");
         });
       }
+      [
+        testcase.addressFieldDetails,
+        testcase.creditCardFieldDetails,
+        testcase.validFieldDetails,
+      ].forEach(details => setElementWeakRef(details));
 
-      verifyDetails(handler.addressFieldDetails, testcase.addressFieldDetails);
-      verifyDetails(handler.creditCardFieldDetails, testcase.creditCardFieldDetails);
+      let handler = new FormAutofillHandler(formLike);
+      let validFieldDetails = handler.collectFormFields();
 
-      Assert.equal(handler.isValidAddressForm, testcase.isValidForm.address, "Valid Address Form Checking");
-      Assert.equal(handler.isValidCreditCardForm, testcase.isValidForm.creditCard, "Valid Credit Card Form Checking");
+      verifyDetails(handler.address.fieldDetails, testcase.addressFieldDetails);
+      verifyDetails(handler.creditCard.fieldDetails, testcase.creditCardFieldDetails);
+      verifyDetails(validFieldDetails, testcase.validFieldDetails);
     });
   })();
 }