Bug 1417803 - Part 1: Use activeSection to record the current focused field or section. r=lchang,ralin
☠☠ backed out by 7521d3eb77f9 ☠ ☠
authorSean Lee <selee@mozilla.com>
Tue, 05 Dec 2017 14:11:36 +0800
changeset 448453 fe9d556bdef4212cac1a61296412fe22d7fb54fc
parent 448452 8d680183148c2a04879c05ae2d5c6ae3d5540f91
child 448454 cd5e96cab7c584373e7db9dad6422e501ea9d070
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslchang, ralin
bugs1417803
milestone59.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 1417803 - Part 1: Use activeSection to record the current focused field or section. r=lchang,ralin MozReview-Commit-ID: 4mhmTcJOOz2
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -101,18 +101,19 @@ AutofillProfileAutoCompleteSearch.protot
 
     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.getAllFieldNames(focusedInput);
-    let filledRecordGUID = handler.getFilledRecordGUID(focusedInput);
+    let activeSection = handler.activeSection;
+    let allFieldNames = activeSection.allFieldNames;
+    let filledRecordGUID = activeSection.getFilledRecordGUID();
     let searchPermitted = isAddressField ?
                           FormAutofillUtils.isAutofillAddressesEnabled :
                           FormAutofillUtils.isAutofillCreditCardsEnabled;
     let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult;
 
     ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = focusedInput;
     // Fallback to form-history if ...
     //   - specified autofill feature is pref off.
@@ -157,17 +158,17 @@ AutofillProfileAutoCompleteSearch.protot
 
     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);
 
-      let adaptedRecords = handler.getAdaptedProfiles(records, focusedInput);
+      let adaptedRecords = activeSection.getAdaptedProfiles(records);
       let result = null;
       let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
 
       result = new AutocompleteResult(searchString,
                                       info.fieldName,
                                       allFieldNames,
                                       adaptedRecords,
                                       {isSecure, isInputAutofilled});
@@ -294,30 +295,30 @@ let ProfileAutocomplete = {
         this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
       return;
     }
 
     let profile = JSON.parse(this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex));
     let {fieldName} = FormAutofillContent.getInputDetails(focusedInput);
     let formHandler = FormAutofillContent.getFormHandler(focusedInput);
 
-    formHandler.autofillFormFields(profile, focusedInput).then(() => {
+    formHandler.autofillFormFields(profile).then(() => {
       autocompleteController.searchString = profile[fieldName];
     });
   },
 
   _clearProfilePreview() {
     let focusedInput = formFillController.focusedInput || this.lastProfileAutoCompleteFocusedInput;
     if (!focusedInput || !FormAutofillContent.getFormDetails(focusedInput)) {
       return;
     }
 
     let formHandler = FormAutofillContent.getFormHandler(focusedInput);
 
-    formHandler.clearPreviewedFormFields(focusedInput);
+    formHandler.activeSection.clearPreviewedFormFields();
   },
 
   _previewSelectedProfile(selectedIndex) {
     let focusedInput = formFillController.focusedInput;
     if (!focusedInput || !FormAutofillContent.getFormDetails(focusedInput)) {
       // The observer notification is for a different process/frame.
       return;
     }
@@ -325,17 +326,17 @@ let ProfileAutocomplete = {
     if (!this.lastProfileAutoCompleteResult ||
         this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile") {
       return;
     }
 
     let profile = JSON.parse(this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex));
     let formHandler = FormAutofillContent.getFormHandler(focusedInput);
 
-    formHandler.previewFormFields(profile, focusedInput);
+    formHandler.activeSection.previewFormFields(profile);
   },
 };
 
 /**
  * Handles content's interactions for the process.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
@@ -484,54 +485,56 @@ var FormAutofillContent = {
    */
   getFormDetails(element) {
     let formHandler = this.getFormHandler(element);
     return formHandler ? formHandler.fieldDetails : null;
   },
 
   getAllFieldNames(element) {
     let formHandler = this.getFormHandler(element);
-    return formHandler ? formHandler.getAllFieldNames(element) : null;
+    return formHandler ? formHandler.activeSection.allFieldNames : null;
   },
 
   identifyAutofillFields(element) {
     this.log.debug("identifyAutofillFields:", "" + element.ownerDocument.location);
 
     if (!this.savedFieldNames) {
       this.log.debug("identifyAutofillFields: savedFieldNames are not known yet");
       Services.cpmm.sendAsyncMessage("FormAutofill:InitStorage");
     }
 
     let formHandler = this.getFormHandler(element);
     if (!formHandler) {
       let formLike = FormLikeFactory.createFromField(element);
       formHandler = new FormAutofillHandler(formLike);
     } else if (!formHandler.updateFormIfNeeded(element)) {
+      formHandler.focusedInput = element;
       this.log.debug("No control is removed or inserted since last collection.");
       return;
     }
 
     let validDetails = formHandler.collectFormFields();
 
     this._formsDetails.set(formHandler.form.rootElement, formHandler);
     this.log.debug("Adding form handler to _formsDetails:", formHandler);
 
     validDetails.forEach(detail =>
       this._markAsAutofillField(detail.elementWeakRef.get())
     );
+    formHandler.focusedInput = element;
   },
 
   clearForm() {
     let focusedInput = formFillController.focusedInput || ProfileAutocomplete._lastAutoCompleteFocusedInput;
     if (!focusedInput) {
       return;
     }
 
     let formHandler = this.getFormHandler(focusedInput);
-    formHandler.clearPopulatedForm(focusedInput);
+    formHandler.activeSection.clearPopulatedForm();
     autocompleteController.searchString = "";
   },
 
   previewProfile(doc) {
     let docWin = doc.ownerGlobal;
     let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin);
     let lastAutoCompleteResult = ProfileAutocomplete.lastProfileAutoCompleteResult;
     let focusedInput = formFillController.focusedInput;
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -94,16 +94,20 @@ class FormAutofillSection {
                                   ...(this.creditCard.fieldDetails));
     log.debug(this._validDetails.length, "valid fields in the section is collected.");
   }
 
   get validDetails() {
     return this._validDetails;
   }
 
+  set focusedInput(element) {
+    this._focusedDetail = this.getFieldDetailByElement(element);
+  }
+
   getFieldDetailByElement(element) {
     return this._validDetails.find(
       detail => detail.elementWeakRef.get() == element
     );
   }
 
   _isValidCreditCardForm(fieldDetails) {
     let ccNumberReason = "";
@@ -133,37 +137,37 @@ class FormAutofillSection {
     }
     return this._cacheValue.allFieldNames;
   }
 
   getFieldDetailByName(fieldName) {
     return this._validDetails.find(detail => detail.fieldName == fieldName);
   }
 
-  _getTargetSet(element) {
-    let fieldDetail = this.getFieldDetailByElement(element);
+  _getTargetSet() {
+    let fieldDetail = this._focusedDetail;
     if (!fieldDetail) {
       return null;
     }
     if (FormAutofillUtils.isAddressField(fieldDetail.fieldName)) {
       return this.address;
     }
     if (FormAutofillUtils.isCreditCardField(fieldDetail.fieldName)) {
       return this.creditCard;
     }
     return null;
   }
 
-  getFieldDetailsByElement(element) {
-    let targetSet = this._getTargetSet(element);
+  _getFieldDetails() {
+    let targetSet = this._getTargetSet();
     return targetSet ? targetSet.fieldDetails : [];
   }
 
-  getFilledRecordGUID(element) {
-    let targetSet = this._getTargetSet(element);
+  getFilledRecordGUID() {
+    let targetSet = this._getTargetSet();
     return targetSet ? targetSet.filledRecordGUID : null;
   }
 
   _getOneLineStreetAddress(address) {
     if (!this._cacheValue.oneLineStreetAddress) {
       this._cacheValue.oneLineStreetAddress = {};
     }
     if (!this._cacheValue.oneLineStreetAddress[address]) {
@@ -361,26 +365,23 @@ class FormAutofillSection {
   }
 
   /**
    * Processes form fields that can be autofilled, and populates them with the
    * profile provided by backend.
    *
    * @param {Object} profile
    *        A profile to be filled in.
-   * @param {HTMLElement} focusedInput
-   *        A focused input element needed to determine the address or credit
-   *        card field.
    */
-  async autofillFields(profile, focusedInput) {
-    let focusedDetail = this.getFieldDetailByElement(focusedInput);
+  async autofillFields(profile) {
+    let focusedDetail = this._focusedDetail;
     if (!focusedDetail) {
       throw new Error("No fieldDetail for the focused input.");
     }
-    let targetSet = this._getTargetSet(focusedInput);
+    let targetSet = this._getTargetSet();
     if (FormAutofillUtils.isCreditCardField(focusedDetail.fieldName)) {
       // When Master Password is enabled by users, the decryption process
       // should prompt Master Password dialog to get the decrypted credit
       // card number. Otherwise, the number can be decrypted with the default
       // password.
       if (profile["cc-number-encrypted"]) {
         let decrypted = await this._decrypt(profile["cc-number-encrypted"], true);
 
@@ -410,16 +411,17 @@ class FormAutofillSection {
       element.previewValue = "";
       let value = profile[fieldDetail.fieldName];
 
       if (element instanceof Ci.nsIDOMHTMLInputElement && value) {
         // For the focused input element, it will be filled with a valid value
         // anyway.
         // For the others, the fields should be only filled when their values
         // are empty.
+        let focusedInput = focusedDetail.elementWeakRef.get();
         if (element == focusedInput ||
             (element != focusedInput && !element.value)) {
           element.setUserInput(value);
           this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
         }
       } else if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
         let cache = this._cacheValue.matchingSelectOption.get(element) || {};
         let option = cache[value] && cache[value].get();
@@ -442,29 +444,27 @@ class FormAutofillSection {
     }
   }
 
   /**
    * Populates result to the preview layers with given profile.
    *
    * @param {Object} profile
    *        A profile to be previewed with
-   * @param {HTMLElement} focusedInput
-   *        A focused input element for determining credit card or address fields.
    */
-  previewFormFields(profile, focusedInput) {
+  previewFormFields(profile) {
     log.debug("preview profile: ", profile);
 
     // Always show the decrypted credit card number when Master Password is
     // disabled.
     if (profile["cc-number-decrypted"]) {
       profile["cc-number"] = profile["cc-number-decrypted"];
     }
 
-    let fieldDetails = this.getFieldDetailsByElement(focusedInput);
+    let fieldDetails = this._getFieldDetails();
     for (let fieldDetail of fieldDetails) {
       let element = fieldDetail.elementWeakRef.get();
       let value = profile[fieldDetail.fieldName] || "";
 
       // Skip the field that is null
       if (!element) {
         continue;
       }
@@ -487,24 +487,21 @@ class FormAutofillSection {
       }
       element.previewValue = value;
       this.changeFieldState(fieldDetail, value ? FIELD_STATES.PREVIEW : FIELD_STATES.NORMAL);
     }
   }
 
   /**
    * Clear preview text and background highlight of all fields.
-   *
-   * @param {HTMLElement} focusedInput
-   *        A focused input element for determining credit card or address fields.
    */
-  clearPreviewedFormFields(focusedInput) {
+  clearPreviewedFormFields() {
     log.debug("clear previewed fields in:", this.form);
 
-    let fieldDetails = this.getFieldDetailsByElement(focusedInput);
+    let fieldDetails = this._getFieldDetails();
     for (let fieldDetail of fieldDetails) {
       let element = fieldDetail.elementWeakRef.get();
       if (!element) {
         log.warn(fieldDetail.fieldName, "is unreachable");
         continue;
       }
 
       element.previewValue = "";
@@ -516,22 +513,19 @@ class FormAutofillSection {
       }
 
       this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
     }
   }
 
   /**
    * Clear value and highlight style of all filled fields.
-   *
-   * @param {Object} focusedInput
-   *        A focused input element for determining credit card or address fields.
    */
-  clearPopulatedForm(focusedInput) {
-    let fieldDetails = this.getFieldDetailsByElement(focusedInput);
+  clearPopulatedForm() {
+    let fieldDetails = this._getFieldDetails();
     for (let fieldDetail of fieldDetails) {
       let element = fieldDetail.elementWeakRef.get();
       if (!element) {
         log.warn(fieldDetail.fieldName, "is unreachable");
         continue;
       }
 
       // Only reset value for input element.
@@ -802,16 +796,36 @@ class FormAutofillHandler {
       .getInterface(Ci.nsIDOMWindowUtils);
 
     /**
      * Time in milliseconds since epoch when a user started filling in the form.
      */
     this.timeStartedFillingMS = null;
   }
 
+  set focusedInput(element) {
+    let section = this._sectionCache.get(element);
+    if (!section) {
+      section = this.sections.find(
+        s => s.getFieldDetailByElement(element)
+      );
+      this._sectionCache.set(element, section);
+    }
+
+    this._focusedSection = section;
+
+    if (section) {
+      section.focusedInput = element;
+    }
+  }
+
+  get activeSection() {
+    return this._focusedSection;
+  }
+
   /**
    * Check the form is necessary to be updated. This function should be able to
    * detect any changes including all control elements in the form.
    * @param {HTMLElement} element The element supposed to be in the form.
    * @returns {boolean} FormAutofillHandler.form is updated or not.
    */
   updateFormIfNeeded(element) {
     // When the following condition happens, FormAutofillHandler.form should be
@@ -904,75 +918,30 @@ class FormAutofillHandler {
       }
       input.addEventListener("input", this, {mozSystemGroup: true});
     }
 
     this.fieldDetails = allValidDetails;
     return allValidDetails;
   }
 
-  getSectionByElement(element) {
-    let section = this._sectionCache.get(element);
-    if (!section) {
-      section = this.sections.find(
-        s => s.getFieldDetailByElement(element)
-      );
-      this._sectionCache.set(element, section);
-    }
-    return section;
-  }
-
-  getAllFieldNames(focusedInput) {
-    let section = this.getSectionByElement(focusedInput);
-    return section.allFieldNames;
-  }
-
-  previewFormFields(profile, focusedInput) {
-    let section = this.getSectionByElement(focusedInput);
-    section.previewFormFields(profile, focusedInput);
-  }
-
-  clearPreviewedFormFields(focusedInput) {
-    let section = this.getSectionByElement(focusedInput);
-    section.clearPreviewedFormFields(focusedInput);
-  }
-
-  clearPopulatedForm(focusedInput) {
-    let section = this.getSectionByElement(focusedInput);
-    section.clearPopulatedForm(focusedInput);
-  }
-
-  getFilledRecordGUID(focusedInput) {
-    let section = this.getSectionByElement(focusedInput);
-    return section.getFilledRecordGUID(focusedInput);
-  }
-
-  getAdaptedProfiles(originalProfiles, focusedInput) {
-    let section = this.getSectionByElement(focusedInput);
-    section.getAdaptedProfiles(originalProfiles);
-    return originalProfiles;
-  }
-
   hasFilledSection() {
     return this.sections.some(section => section.isFilled());
   }
 
   /**
    * Processes form fields that can be autofilled, and populates them with the
    * profile provided by backend.
    *
    * @param {Object} profile
    *        A profile to be filled in.
-   * @param {HTMLElement} focusedInput
-   *        A focused input element needed to determine the address or credit
-   *        card field.
    */
-  async autofillFormFields(profile, focusedInput) {
+  async autofillFormFields(profile) {
     let noFilledSectionsPreviously = !this.hasFilledSection();
-    await this.getSectionByElement(focusedInput).autofillFields(profile, focusedInput);
+    await this.activeSection.autofillFields(profile);
 
     const onChangeHandler = e => {
       if (!e.isTrusted) {
         return;
       }
       if (e.type == "reset") {
         for (let section of this.sections) {
           section.resetFieldStates();
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -532,17 +532,18 @@ function do_test(testcases, testFn) {
             // Avoid waiting for `change` event of a input with a blank value to
             // be filled.
             return;
           }
           promises.push(...testFn(testcase, element));
         });
 
         let focusedInput = doc.getElementById(testcase.focusedInputId);
-        let [adaptedProfile] = handler.getAdaptedProfiles([testcase.profileData], focusedInput);
+        handler.focusedInput = focusedInput;
+        let [adaptedProfile] = handler.activeSection.getAdaptedProfiles([testcase.profileData]);
         await handler.autofillFormFields(adaptedProfile, focusedInput);
         Assert.equal(handlerInfo.filledRecordGUID, testcase.profileData.guid,
                      "Check if filledRecordGUID is set correctly");
         await Promise.all(promises);
       });
     })();
   }
 }
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -929,30 +929,29 @@ for (let testcase of TESTCASES) {
 
     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);
 
     handler.collectFormFields();
-    let focusedInput = form.elements[0];
-    let adaptedRecords = handler.getAdaptedProfiles(testcase.profileData, focusedInput);
+    handler.focusedInput = form.elements[0];
+    let adaptedRecords = handler.activeSection.getAdaptedProfiles(testcase.profileData);
     Assert.deepEqual(adaptedRecords, testcase.expectedResult);
 
     if (testcase.expectedOptionElements) {
       testcase.expectedOptionElements.forEach((expectedOptionElement, i) => {
         for (let field in expectedOptionElement) {
           let select = form.querySelector(`[autocomplete=${field}]`);
           let expectedOption = doc.getElementById(expectedOptionElement[field]);
           Assert.notEqual(expectedOption, null);
 
           let value = testcase.profileData[i][field];
-          let section = handler.getSectionByElement(select);
-          let cache = section._cacheValue.matchingSelectOption.get(select);
+          let cache = handler.activeSection._cacheValue.matchingSelectOption.get(select);
           let targetOption = cache[value] && cache[value].get();
           Assert.notEqual(targetOption, null);
 
           Assert.equal(targetOption, expectedOption);
         }
       });
     }
   });