Bug 1300996 - Part 2: Show preview text on and highlight the fields that would be filled. r=MattN, lchang
☠☠ backed out by 42e1d7155891 ☠ ☠
authorRay Lin <ralin@mozilla.com>
Mon, 24 Apr 2017 10:55:29 +0800
changeset 410781 da12ceebe125788134a169e5fac186685423dd2b
parent 410780 f54fca58d189cfc259766817faaa3a962ad96933
child 410782 71ef10fe925e44b0b3e3ad10efeab9931d7bf39e
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN, lchang
bugs1300996
milestone55.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 1300996 - Part 2: Show preview text on and highlight the fields that would be filled. r=MattN, lchang MozReview-Commit-ID: DMgVhz2lvZ1
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/skin/shared/autocomplete-item.css
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -24,16 +24,18 @@ 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);
 }
 
 FormAutofillHandler.prototype = {
   /**
    * DOM Form element to which this object is attached.
    */
   form: null,
 
@@ -53,16 +55,33 @@ FormAutofillHandler.prototype = {
   fieldDetails: null,
 
   /**
    * String of the filled profile's guid.
    */
   filledProfileGUID: null,
 
   /**
+   * A WindowUtils reference of which Window the form belongs
+   */
+  winUtils: null,
+
+  /**
+   * Enum for form autofill MANUALLY_MANAGED_STATES values
+   */
+  fieldStateEnum: {
+    // not themed
+    NORMAL: null,
+    // highlighted
+    AUTO_FILLED: "-moz-autofill",
+    // highlighted && grey color text
+    PREVIEW: "-moz-autofill-preview",
+  },
+
+  /**
    * Set fieldDetails from the form about fields that can be autofilled.
    */
   collectFormFields() {
     let fieldDetails = FormAutofillHeuristics.getFormInfo(this.form);
     this.fieldDetails = fieldDetails ? fieldDetails : [];
     log.debug("Collected details on", this.fieldDetails.length, "fields");
   },
 
@@ -82,81 +101,131 @@ FormAutofillHandler.prototype = {
     for (let fieldDetail of this.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 || element === focusedInput) {
+      if (!element) {
         continue;
       }
 
       let value = profile[fieldDetail.fieldName];
-      if (element instanceof Ci.nsIDOMHTMLInputElement && value) {
-        if (element.value) {
-          continue;
+      if (element instanceof Ci.nsIDOMHTMLInputElement && !element.value && value) {
+        if (element !== focusedInput) {
+          element.setUserInput(value);
         }
-        element.setUserInput(value);
+        this.changeFieldState(fieldDetail, "AUTO_FILLED");
       } else if (element instanceof Ci.nsIDOMHTMLSelectElement) {
         for (let option of element.options) {
           if (value === option.textContent || value === option.value) {
             // Do not change value if the option is already selected.
             // Use case for multiple select is not considered here.
             if (option.selected) {
               break;
             }
             // TODO: Using dispatchEvent does not 100% simulate select change.
             //       Should investigate further in Bug 1365895.
             option.selected = true;
             element.dispatchEvent(new Event("input", {"bubbles": true}));
             element.dispatchEvent(new Event("change", {"bubbles": true}));
+            this.changeFieldState(fieldDetail, "AUTO_FILLED");
             break;
           }
         }
       }
+      element.previewValue = "";
     }
   },
 
   /**
    * 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.fieldDetails) {
+      let element = fieldDetail.elementWeakRef.get();
       let value = profile[fieldDetail.fieldName] || "";
 
-      // Skip the fields that already has text entered
-      if (fieldDetail.element.value) {
+      // Skip the field that is null or already has text entered
+      if (!element || element.value) {
+        continue;
+      }
+
+      element.previewValue = value;
+      this.changeFieldState(fieldDetail, value ? "PREVIEW" : "NORMAL");
+    }
+  },
+
+  /**
+   * Clear preview text and background highlight of all fields.
+   */
+  clearPreviewedFormFields() {
+    log.debug("clear previewed fields in:", this.form);
+
+    for (let fieldDetail of this.fieldDetails) {
+      let element = fieldDetail.elementWeakRef.get();
+      if (!element) {
+        log.warn(fieldDetail.fieldName, "is unreachable");
+        continue;
+      }
+
+      element.previewValue = "";
+
+      // We keep the state if this field has
+      // already been auto-filled.
+      if (fieldDetail.state === "AUTO_FILLED") {
         continue;
       }
 
-      // TODO: Set highlight style and preview text.
+      this.changeFieldState(fieldDetail, "NORMAL");
     }
-    */
   },
 
-  clearPreviewedFormFields() {
-    log.debug("clear previewed fields in:", this.form);
-    /*
-    for (let fieldDetail of this.fieldDetails) {
-      // TODO: Clear preview text
+  /**
+   * Change the state of a field to correspond with different presentations.
+   *
+   * @param {Object} fieldDetail
+   *        A fieldDetail of which its element is about to update the state.
+   * @param {string} nextState
+   *        Used to determine the next state
+   */
+  changeFieldState(fieldDetail, nextState) {
+    let element = fieldDetail.elementWeakRef.get();
 
-      // We keep the highlight of all fields if this form has
-      // already been auto-filled with a profile.
-      if (this.filledProfileGUID == null) {
-        // TODO: Remove highlight style
+    if (!element) {
+      log.warn(fieldDetail.fieldName, "is unreachable while changing state");
+      return;
+    }
+    if (!(nextState in this.fieldStateEnum)) {
+      log.warn(fieldDetail.fieldName, "is trying to change to an invalid state");
+      return;
+    }
+
+    for (let [state, mmStateValue] of Object.entries(this.fieldStateEnum)) {
+      // The NORMAL state is simply the absence of other manually
+      // managed states so we never need to add or remove it.
+      if (!mmStateValue) {
+        continue;
+      }
+
+      if (state == nextState) {
+        this.winUtils.addManuallyManagedState(element, mmStateValue);
+      } else {
+        this.winUtils.removeManuallyManagedState(element, mmStateValue);
       }
     }
-    */
+
+    fieldDetail.state = nextState;
   },
 
   /**
    * 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.
    */
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -30,16 +30,17 @@ xul|richlistitem[originaltype="autofill-
   border-bottom: 1px solid rgba(38,38,38,.15);
   padding: var(--item-padding-vertical) 0;
   padding-inline-start: var(--item-padding-horizontal);
   padding-inline-end: var(--item-padding-horizontal);
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
   align-items: center;
+  background-color: #FFFFFF;
   color: -moz-FieldText
 }
 
 .profile-item-box:last-child {
   border-bottom: 0;
 }
 
 .profile-item-box > .profile-item-col {