Bug 1429718 - Reset AutocompleteController state every startSearch for formautofill to prevent any result being cached. r=lchang draft
authorRay Lin <ralin@mozilla.com>
Tue, 16 Jan 2018 18:23:53 +0800
changeset 721394 0fdc68fb1c0f2b64ed6cb6f32a93029ce2d05a8e
parent 721208 b2cb61e83ac50115a28f04aaa8a32d4db90aad23
child 746337 b5bcc51378c6cdeb18947da200a1b8aa9541b51e
push id95840
push userbmo:ralin@mozilla.com
push dateWed, 17 Jan 2018 09:52:35 +0000
reviewerslchang
bugs1429718
milestone59.0a1
Bug 1429718 - Reset AutocompleteController state every startSearch for formautofill to prevent any result being cached. r=lchang MozReview-Commit-ID: HSOSsdbaH0b
browser/extensions/formautofill/FormAutofillContent.jsm
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -103,78 +103,77 @@ AutofillProfileAutoCompleteSearch.protot
     let isAddressField = FormAutofillUtils.isAddressField(activeFieldDetail.fieldName);
     let isInputAutofilled = activeFieldDetail.state == FIELD_STATES.AUTO_FILLED;
     let allFieldNames = activeSection.allFieldNames;
     let filledRecordGUID = activeSection.filledRecordGUID;
     let searchPermitted = isAddressField ?
                           FormAutofillUtils.isAutofillAddressesEnabled :
                           FormAutofillUtils.isAutofillCreditCardsEnabled;
     let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult;
+    let pendingSearchResult = null;
 
     ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = activeInput;
     // 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(activeFieldDetail.fieldName) ||
         (!isInputAutofilled && filledRecordGUID) || (isAddressField &&
         allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
       if (activeInput.autocomplete == "off") {
         // Create a dummy result as an empty search result.
-        let result = new AutocompleteResult("", "", [], [], {});
-        listener.onSearchResult(this, result);
-        return;
+        pendingSearchResult = new AutocompleteResult("", "", [], [], {});
+      } else {
+        pendingSearchResult = new Promise(resolve => {
+          let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
+                            .createInstance(Ci.nsIAutoCompleteSearch);
+          formHistory.startSearch(searchString, searchParam, previousResult, {
+            onSearchResult: (_, result) => resolve(result),
+          });
+        });
       }
-      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.lastProfileAutoCompleteResult = result;
-        },
-      });
-      return;
-    }
-
-    if (isInputAutofilled) {
-      let result = new AutocompleteResult(searchString, "", [], [], {isInputAutofilled});
-      listener.onSearchResult(this, result);
-      ProfileAutocomplete.lastProfileAutoCompleteResult = result;
-      return;
-    }
+    } else if (isInputAutofilled) {
+      pendingSearchResult = new AutocompleteResult(searchString, "", [], [], {isInputAutofilled});
+    } else {
+      let infoWithoutElement = {...activeFieldDetail};
+      delete infoWithoutElement.elementWeakRef;
 
-    let infoWithoutElement = Object.assign({}, activeFieldDetail);
-    delete infoWithoutElement.elementWeakRef;
-
-    let data = {
-      collectionName: isAddressField ? ADDRESSES_COLLECTION_NAME : CREDITCARDS_COLLECTION_NAME,
-      info: infoWithoutElement,
-      searchString,
-    };
+      let data = {
+        collectionName: isAddressField ? ADDRESSES_COLLECTION_NAME : CREDITCARDS_COLLECTION_NAME,
+        info: infoWithoutElement,
+        searchString,
+      };
 
-    this._getRecords(data).then((records) => {
-      if (this.forceStop) {
-        return;
-      }
-      // Sort addresses by timeLastUsed for showing the lastest used address at top.
-      records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
+      pendingSearchResult = this._getRecords(data).then((records) => {
+        if (this.forceStop) {
+          return null;
+        }
+        // Sort addresses by timeLastUsed for showing the lastest used address at top.
+        records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
 
-      let adaptedRecords = activeSection.getAdaptedProfiles(records);
-      let result = null;
-      let handler = FormAutofillContent.activeHandler;
-      let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
+        let adaptedRecords = activeSection.getAdaptedProfiles(records);
+        let handler = FormAutofillContent.activeHandler;
+        let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
 
-      result = new AutocompleteResult(searchString,
+        return new AutocompleteResult(searchString,
                                       activeFieldDetail.fieldName,
                                       allFieldNames,
                                       adaptedRecords,
                                       {isSecure, isInputAutofilled});
+      });
+    }
+
+    Promise.resolve(pendingSearchResult).then((result) => {
       listener.onSearchResult(this, result);
       ProfileAutocomplete.lastProfileAutoCompleteResult = result;
+      // Reset AutoCompleteController's state at the end of startSearch to ensure that
+      // none of form autofill result will be cached in other places and make the
+      // result out of sync.
+      autocompleteController.resetInternalState();
     });
   },
 
   /**
    * Stops an asynchronous search that is in progress
    */
   stopSearch() {
     ProfileAutocomplete.lastProfileAutoCompleteResult = null;
@@ -288,21 +287,18 @@ let ProfileAutocomplete = {
     let selectedIndex = this._getSelectedIndex(focusedInput.ownerGlobal);
     if (selectedIndex == -1 ||
         !this.lastProfileAutoCompleteResult ||
         this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
       return;
     }
 
     let profile = JSON.parse(this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex));
-    let {fieldName} = FormAutofillContent.activeFieldDetail;
 
-    FormAutofillContent.activeHandler.autofillFormFields(profile).then(() => {
-      autocompleteController.searchString = profile[fieldName];
-    });
+    FormAutofillContent.activeHandler.autofillFormFields(profile);
   },
 
   _clearProfilePreview() {
     if (!this.lastProfileAutoCompleteFocusedInput || !FormAutofillContent.activeSection) {
       return;
     }
 
     FormAutofillContent.activeSection.clearPreviewedFormFields();
@@ -560,17 +556,16 @@ var FormAutofillContent = {
 
   clearForm() {
     let focusedInput = this.activeInput || ProfileAutocomplete._lastAutoCompleteFocusedInput;
     if (!focusedInput) {
       return;
     }
 
     this.activeSection.clearPopulatedForm();
-    autocompleteController.searchString = "";
   },
 
   previewProfile(doc) {
     let docWin = doc.ownerGlobal;
     let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin);
     let lastAutoCompleteResult = ProfileAutocomplete.lastProfileAutoCompleteResult;
     let focusedInput = this.activeInput;
     let mm = this._messageManagerFromWindow(docWin);