Bug 1420169 - Move the ownership of input handler from form to section. r=lchang draft
authorRay Lin <ralin@mozilla.com>
Fri, 24 Nov 2017 01:01:45 +0800
changeset 703627 5c584866705cc7e2935dfdff1637661b9d60c286
parent 703626 f79c83cf19707869e4f3bbbaf5691782e33f85cd
child 703629 05053d95384a45aca0e7c1c117ff6da0e27640a6
push id90894
push userbmo:ralin@mozilla.com
push dateMon, 27 Nov 2017 08:21:14 +0000
reviewerslchang
bugs1420169
milestone59.0a1
Bug 1420169 - Move the ownership of input handler from form to section. r=lchang MozReview-Commit-ID: FZgLNAqg1hR
browser/extensions/formautofill/FormAutofillHandler.jsm
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -388,36 +388,36 @@ class FormAutofillSection {
         // 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.
         if (element == focusedInput ||
             (element != focusedInput && !element.value)) {
           element.setUserInput(value);
           this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
-          continue;
         }
-      }
-
-      if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
+      } else if (ChromeUtils.getClassName(element) === "HTMLSelectElement") {
         let cache = this._cacheValue.matchingSelectOption.get(element) || {};
         let option = cache[value] && cache[value].get();
         if (!option) {
           continue;
         }
         // Do not change value or dispatch events if the option is already selected.
         // Use case for multiple select is not considered here.
         if (!option.selected) {
           option.selected = true;
           element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
           element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
         }
         // Autofill highlight appears regardless if value is changed or not
         this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
       }
+      if (fieldDetail.state == FIELD_STATES.AUTO_FILLED) {
+        element.addEventListener("input", this);
+      }
     }
   }
 
   /**
    * Populates result to the preview layers with given profile.
    *
    * @param {Object} profile
    *        A profile to be previewed with
@@ -549,28 +549,20 @@ class FormAutofillSection {
       } else {
         this.winUtils.removeManuallyManagedState(element, mmStateValue);
       }
     }
 
     fieldDetail.state = nextState;
   }
 
-  clearFieldState(focusedInput) {
-    let fieldDetail = this.getFieldDetailByElement(focusedInput);
-    this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
-    let targetSet = this._getTargetSet(focusedInput);
-
-    if (!targetSet.fieldDetails.some(detail => detail.state == FIELD_STATES.AUTO_FILLED)) {
-      targetSet.filledRecordGUID = null;
-    }
-  }
-
   resetFieldStates() {
     for (let fieldDetail of this._validDetails) {
+      const element = fieldDetail.elementWeakRef.get();
+      element.removeEventListener("input", this);
       this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
     }
     this.address.filledRecordGUID = null;
     this.creditCard.filledRecordGUID = null;
   }
 
   isFilled() {
     return !!(this.address.filledRecordGUID || this.creditCard.filledRecordGUID);
@@ -738,16 +730,36 @@ class FormAutofillSection {
       Services.cpmm.addMessageListener("FormAutofill:DecryptedString", function getResult(result) {
         Services.cpmm.removeMessageListener("FormAutofill:DecryptedString", getResult);
         resolve(result.data);
       });
 
       Services.cpmm.sendAsyncMessage("FormAutofill:GetDecryptedString", {cipherText, reauth});
     });
   }
+
+  handleEvent(event) {
+    switch (event.type) {
+      case "input": {
+        if (!event.isTrusted) {
+          return;
+        }
+        const target = event.target;
+        const fieldDetail = this.getFieldDetailByElement(target);
+        const targetSet = this._getTargetSet(target);
+        this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
+
+        if (!targetSet.fieldDetails.some(detail => detail.state == FIELD_STATES.AUTO_FILLED)) {
+          targetSet.filledRecordGUID = null;
+        }
+        target.removeEventListener("input", this);
+        break;
+      }
+    }
+  }
 }
 
 /**
  * Handles profile autofill for a DOM Form element.
  */
 class FormAutofillHandler {
   /**
    * Initialize the form from `FormLike` object to handle the section or form
@@ -923,43 +935,38 @@ class FormAutofillHandler {
    *
    * @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) {
-    let noFilledSections = !this.hasFilledSection();
+    let noFilledSectionsPreviously = !this.hasFilledSection();
     await this.getSectionByElement(focusedInput).autofillFields(profile, focusedInput);
 
-    // Handle the highlight style resetting caused by user's correction afterward.
-    log.debug("register change handler for filled form:", this.form);
     const onChangeHandler = e => {
       if (!e.isTrusted) {
         return;
       }
-
-      if (e.type == "input") {
-        let section = this.getSectionByElement(e.target);
-        section.clearFieldState(e.target);
-      } else if (e.type == "reset") {
+      if (e.type == "reset") {
         for (let section of this.sections) {
           section.resetFieldStates();
         }
       }
-
       // Unregister listeners once no field is in AUTO_FILLED state.
       if (!this.hasFilledSection()) {
         this.form.rootElement.removeEventListener("input", onChangeHandler);
         this.form.rootElement.removeEventListener("reset", onChangeHandler);
       }
     };
 
-    if (noFilledSections) {
+    if (noFilledSectionsPreviously) {
+      // Handle the highlight style resetting caused by user's correction afterward.
+      log.debug("register change handler for filled form:", this.form);
       this.form.rootElement.addEventListener("input", onChangeHandler);
       this.form.rootElement.addEventListener("reset", onChangeHandler);
     }
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "input":