Bug 1330111 - Automatically open login autocomplete from username fields if the form isn't filled. r=johannh draft
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 03 Feb 2017 12:41:51 -0800
changeset 478923 2ab480cdfdfdc7fea7e329e22b9540e1918c4594
parent 478922 829c2dfbc279b4093368c3c4dc27219bdc6ee612
child 478924 c69d7e354123ed1dc5355d1c8ba436a92b12be00
child 479584 bb4200281706bc5bdc7824eb9a9f0db15be2fbaf
push id44090
push usermozilla@noorenberghe.ca
push dateSat, 04 Feb 2017 01:25:53 +0000
reviewersjohannh
bugs1330111
milestone54.0a1
Bug 1330111 - Automatically open login autocomplete from username fields if the form isn't filled. r=johannh MozReview-Commit-ID: BWJcc0ZaA3K
toolkit/components/passwordmgr/LoginManagerContent.jsm
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -39,16 +39,17 @@ XPCOMUtils.defineLazyGetter(this, "log",
 
 // These mirror signon.* prefs.
 var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
 
 var observer = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsIFormSubmitObserver,
                                           Ci.nsIWebProgressListener,
+                                          Ci.nsIDOMEventListener,
                                           Ci.nsISupportsWeakReference]),
 
   // nsIFormSubmitObserver
   notify(formElement, aWindow, actionURI) {
     log("observer notified for form submission.");
 
     // We're invoked before the content's |onsubmit| handlers, so we
     // can grab form data before it might be modified (see bug 257781).
@@ -103,16 +104,34 @@ var observer = {
     if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
       log("onStateChange: loadType isn't LOAD_CMD_NORMAL:", aWebProgress.loadType);
       return;
     }
 
     log("onStateChange handled:", channel);
     LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
   },
+
+  handleEvent(aEvent) {
+    if (!aEvent.isTrusted) {
+      return;
+    }
+
+    if (!gEnabled) {
+      return;
+    }
+
+    switch (aEvent.type) {
+      // Only used for username fields.
+      case "focus": {
+        LoginManagerContent._onUsernameFocus(aEvent);
+        break;
+      }
+    }
+  },
 };
 
 Services.obs.addObserver(observer, "earlyformsubmit", false);
 var prefBranch = Services.prefs.getBranch("signon.");
 prefBranch.addObserver("", observer.onPrefChange, false);
 
 observer.onPrefChange(); // read initial values
 
@@ -519,19 +538,41 @@ var LoginManagerContent = {
 
   loginsFound({ form, loginsFound, recipes }) {
     let doc = form.ownerDocument;
     let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
 
     this._fillForm(form, autofillForm, false, false, false, loginsFound, recipes);
   },
 
-  /*
-   * onUsernameInput
-   *
+  /**
+   * Focus event handler for username fields to decide whether to show autocomplete.
+   * @param {FocusEvent} event
+   */
+  _onUsernameFocus(event) {
+    let focusedField = event.target;
+    if (!focusedField.mozIsTextField(true) || focusedField.readOnly) {
+      return;
+    }
+
+    if (this._isLoginAlreadyFilled(focusedField)) {
+      log("_onUsernameFocus: Already filled");
+      return;
+    }
+
+    let formFillFocused = this._formFillService.focusedInput;
+    if (formFillFocused == focusedField) {
+      log("_onUsernameFocus: Opening the autocomplete popup");
+      this._formFillService.showPopup();
+    } else {
+      log("_onUsernameFocus: FormFillController has a different focused input");
+    }
+  },
+
+  /**
    * Listens for DOMAutoComplete and blur events on an input field.
    */
   onUsernameInput(event) {
     if (!event.isTrusted)
       return;
 
     if (!gEnabled)
       return;
@@ -980,18 +1021,16 @@ var LoginManagerContent = {
 
       // Need a valid password field to do anything.
       if (passwordField == null) {
         log("not filling form, no password field found");
         autofillResult = AUTOFILL_RESULT.NO_PASSWORD_FIELD;
         return;
       }
 
-      this._formFillService.markAsLoginManagerField(passwordField);
-
       // If the password field is disabled or read-only, there's nothing to do.
       if (passwordField.disabled || passwordField.readOnly) {
         log("not filling form, password field disabled or read-only");
         autofillResult = AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY;
         return;
       }
 
       // Attach autocomplete stuff to the username field, if we have
@@ -1167,16 +1206,29 @@ var LoginManagerContent = {
       if (autofillResult == -1) {
         // eslint-disable-next-line no-unsafe-finally
         throw new Error("_fillForm: autofillResult must be specified");
       }
 
       if (!userTriggered) {
         // Ignore fills as a result of user action for this probe.
         Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT").add(autofillResult);
+
+        if (usernameField) {
+          let focusedElement = this._formFillService.focusedInput;
+          if (usernameField == focusedElement &&
+              autofillResult !== AUTOFILL_RESULT.FILLED) {
+            log("_fillForm: Opening username autocomplete popup since the form wasn't autofilled");
+            this._formFillService.showPopup();
+          }
+        }
+      }
+
+      if (usernameField) {
+        usernameField.addEventListener("focus", observer);
       }
 
       Services.obs.notifyObservers(form.rootElement, "passwordmgr-processed-form", null);
     }
   },
 
   /**
    * Given a field, determine whether that field was last filled as a username