Bug 1407508 - Part 2. Return clear form result instead of fallback to form history results for filled fields. r=lchang draft
authorRay Lin <ralin@mozilla.com>
Wed, 11 Oct 2017 17:45:46 +0800
changeset 684632 28a125e7b1cac21d7a8b594dbcc1a4d3587f1246
parent 684603 52e50413f36b3e8960547aed1adf3aaf88ca813a
child 684633 9375d3b3196a5a2c6c6f7f91111c6ce9a3a5bcf7
push id85683
push userbmo:ralin@mozilla.com
push dateMon, 23 Oct 2017 10:11:05 +0000
reviewerslchang
bugs1407508
milestone58.0a1
Bug 1407508 - Part 2. Return clear form result instead of fallback to form history results for filled fields. r=lchang MozReview-Commit-ID: Dtc2OHHEvUZ
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -27,17 +27,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://formautofill/FormAutofillHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                   "resource://gre/modules/FormLikeFactory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
                                   "resource://gre/modules/InsecurePasswordUtils.jsm");
 
 const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                              .getService(Ci.nsIFormFillController);
-const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
+const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME, FIELD_STATES} = FormAutofillUtils;
 
 // Register/unregister a constructor as a factory.
 function AutocompleteFactory() {}
 AutocompleteFactory.prototype = {
   register(targetConstructor) {
     let proto = targetConstructor.prototype;
     this._classID = proto.classID;
 
@@ -96,29 +96,31 @@ AutofillProfileAutoCompleteSearch.protot
 
     this.forceStop = false;
 
     let savedFieldNames = FormAutofillContent.savedFieldNames;
 
     let focusedInput = formFillController.focusedInput;
     let info = FormAutofillContent.getInputDetails(focusedInput);
     let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
+    let isInputAutofilled = info.state == FIELD_STATES.AUTO_FILLED;
     let handler = FormAutofillContent.getFormHandler(focusedInput);
     let allFieldNames = handler.allFieldNames;
     let filledRecordGUID = isAddressField ? handler.address.filledRecordGUID : handler.creditCard.filledRecordGUID;
     let searchPermitted = isAddressField ?
                           FormAutofillUtils.isAutofillAddressesEnabled :
                           FormAutofillUtils.isAutofillCreditCardsEnabled;
 
     // Fallback to form-history if ...
     //   - specified autofill feature is pref off.
     //   - no profile can fill the currently-focused input.
     //   - the current form has already been populated.
     //   - (address only) less than 3 inputs are covered by all saved fields in the storage.
-    if (!searchPermitted || !savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
+    if (!searchPermitted || !savedFieldNames.has(info.fieldName) ||
+        (!isInputAutofilled && filledRecordGUID) || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
       if (focusedInput.autocomplete == "off") {
         // Create a dummy AddressResult as an empty search result.
         let result = new AddressResult("", "", [], [], {});
         listener.onSearchResult(this, result);
         return;
       }
       let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
@@ -150,25 +152,25 @@ AutofillProfileAutoCompleteSearch.protot
 
       let adaptedRecords = handler.getAdaptedProfiles(records);
       let result = null;
       if (isAddressField) {
         result = new AddressResult(searchString,
                                    info.fieldName,
                                    allFieldNames,
                                    adaptedRecords,
-                                   {});
+                                   {isInputAutofilled});
       } else {
         let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
 
         result = new CreditCardResult(searchString,
                                       info.fieldName,
                                       allFieldNames,
                                       adaptedRecords,
-                                      {isSecure});
+                                      {isSecure, isInputAutofilled});
       }
       listener.onSearchResult(this, result);
       ProfileAutocomplete.setProfileAutoCompleteResult(result);
     });
   },
 
   /**
    * Stops an asynchronous search that is in progress
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -16,16 +16,17 @@ XPCOMUtils.defineLazyPreferenceGetter(th
 
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 class ProfileAutoCompleteResult {
   constructor(searchString, focusedFieldName, allFieldNames, matchingProfiles, {
     resultCode = null,
     isSecure = true,
+    isInputAutofilled = false,
   }) {
     log.debug("Constructing new ProfileAutoCompleteResult:", [...arguments]);
 
     // nsISupports
     this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult]);
 
     // The user's query string
     this.searchString = searchString;
@@ -34,16 +35,18 @@ class ProfileAutoCompleteResult {
     // The matching profiles contains the information for filling forms.
     this._matchingProfiles = matchingProfiles;
     // The default item that should be entered if none is selected
     this.defaultIndex = 0;
     // The reason the search failed
     this.errorDescription = "";
     // The value used to determine whether the form is secure or not.
     this._isSecure = isSecure;
+    // The value to indicate whether the focused input has been autofilled or not.
+    this._isInputAutofilled = isInputAutofilled;
     // All fillable field names in the form including the field name of the currently-focused input.
     this._allFieldNames = [...this._matchingProfiles.reduce((fieldSet, curProfile) => {
       for (let field of Object.keys(curProfile)) {
         fieldSet.add(field);
       }
 
       return fieldSet;
     }, new Set())].filter(field => allFieldNames.includes(field));
@@ -86,28 +89,38 @@ class ProfileAutoCompleteResult {
    */
   _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
     return "";
   }
 
   _generateLabels(focusedFieldName, allFieldNames, profiles) {}
 
   /**
-   * Retrieves a result
+   * Get the value of the result at the given index.
+   *
+   * Always return empty string for form autofill feature to suppress
+   * AutoCompleteController from autofilling, as we'll populate the
+   * fields on our own.
+   *
    * @param   {number} index The index of the result requested
    * @returns {string} The result at the specified index
    */
   getValueAt(index) {
     this._checkIndexBounds(index);
-    return this._popupLabels[index].primary;
+    return "";
   }
 
   getLabelAt(index) {
     this._checkIndexBounds(index);
-    return JSON.stringify(this._popupLabels[index]);
+
+    let label = this._popupLabels[index];
+    if (typeof label == "string") {
+      return label;
+    }
+    return JSON.stringify(label);
   }
 
   /**
    * Retrieves a comment (metadata instance)
    * @param   {number} index The index of the comment requested
    * @returns {string} The comment at the specified index
    */
   getCommentAt(index) {
@@ -120,16 +133,20 @@ class ProfileAutoCompleteResult {
    * @param   {number} index The index of the style hint requested
    * @returns {string} The style hint at the specified index
    */
   getStyleAt(index) {
     this._checkIndexBounds(index);
     if (index == this.matchCount - 1) {
       return "autofill-footer";
     }
+    if (this._isInputAutofilled) {
+      return "autofill-clear-button";
+    }
+
     return "autofill-profile";
   }
 
   /**
    * Retrieves an image url.
    * @param   {number} index The index of the image url requested
    * @returns {string} The image url at the specified index
    */
@@ -230,16 +247,23 @@ class AddressResult extends ProfileAutoC
         return profile[currentFieldName];
       }
     }
 
     return ""; // Nothing matched.
   }
 
   _generateLabels(focusedFieldName, allFieldNames, profiles) {
+    if (this._isInputAutofilled) {
+      return [
+        {primary: "", secondary: ""}, // Clear button
+        {primary: "", secondary: ""}, // Footer
+      ];
+    }
+
     // Skip results without a primary label.
     let labels = profiles.filter(profile => {
       return !!profile[focusedFieldName];
     }).map(profile => {
       let primaryLabel = profile[focusedFieldName];
       if (focusedFieldName == "street-address" &&
           profile["-moz-street-address-one-line"]) {
         primaryLabel = profile["-moz-street-address-one-line"];
@@ -259,21 +283,16 @@ class AddressResult extends ProfileAutoC
       primary: "",
       secondary: "",
       categories: FormAutofillUtils.getCategoriesFromFieldNames(this._allFieldNames),
       focusedCategory: FormAutofillUtils.getCategoryFromFieldName(this._focusedFieldName),
     });
 
     return labels;
   }
-
-  getValueAt(index) {
-    this._checkIndexBounds(index);
-    return "";
-  }
 }
 
 class CreditCardResult extends ProfileAutoCompleteResult {
   constructor(...args) {
     super(...args);
   }
 
   _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
@@ -330,16 +349,23 @@ class CreditCardResult extends ProfileAu
       if (!insecureWarningEnabled) {
         return [];
       }
       let brandName = FormAutofillUtils.brandBundle.GetStringFromName("brandShortName");
 
       return [FormAutofillUtils.stringBundle.formatStringFromName("insecureFieldWarningDescription", [brandName], 1)];
     }
 
+    if (this._isInputAutofilled) {
+      return [
+        {primary: "", secondary: ""}, // Clear button
+        {primary: "", secondary: ""}, // Footer
+      ];
+    }
+
     // Skip results without a primary label.
     let labels = profiles.filter(profile => {
       return !!profile[focusedFieldName];
     }).map(profile => {
       let primaryAffix;
       let primary = profile[focusedFieldName];
 
       if (focusedFieldName == "cc-number") {
@@ -356,44 +382,22 @@ class CreditCardResult extends ProfileAu
       };
     });
     // Add an empty result entry for footer.
     labels.push({primary: "", secondary: ""});
 
     return labels;
   }
 
-  // Always return empty string for credit card result. Since the decryption might
-  // be required of users' input, we have to suppress AutoCompleteController
-  // from filling encrypted data directly.
-  getValueAt(index) {
-    this._checkIndexBounds(index);
-    return "";
-  }
-
-  getLabelAt(index) {
-    this._checkIndexBounds(index);
-
-    let label = this._popupLabels[index];
-    if (typeof label == "string") {
-      return label;
-    }
-    return JSON.stringify(label);
-  }
-
   getStyleAt(index) {
     this._checkIndexBounds(index);
     if (!this._isSecure && insecureWarningEnabled) {
       return "autofill-insecureWarning";
     }
 
-    if (index == this.matchCount - 1) {
-      return "autofill-footer";
-    }
-
-    return "autofill-profile";
+    return super.getStyleAt(index);
   }
 
   getImageAt(index) {
     this._checkIndexBounds(index);
     return "chrome://formautofill/content/icon-credit-card-generic.svg";
   }
 }