Bug 1120888 - Record the result of password manager form autofill. r=dolske a=lizzard
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Thu, 19 Feb 2015 13:13:14 -0800
changeset 249793 cd0f43f2bb0e26c04fcfc17bf1319bfcadb73760
parent 249792 dc9a1813588e6fc08321aa54ef59c83ee551e63f
child 249794 8934c6b99c47e7fb8b0677d98ec1c674f2cfa4a5
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, lizzard
bugs1120888
milestone37.0a2
Bug 1120888 - Record the result of password manager form autofill. r=dolske a=lizzard
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/telemetry/Histograms.json
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -552,45 +552,78 @@ var LoginManagerContent = {
                                       newPasswordField: mockPassword,
                                       oldPasswordField: mockOldPassword },
                                     { openerWin: opener });
   },
 
   /*
    * _fillform
    *
-   * Fill the form with login information if we can find it. This will find
-   * an array of logins if not given any, otherwise it will use the logins
-   * passed in. The logins are returned so they can be reused for
+   * Fill the form with the provided login information.
+   * The logins are returned so they can be reused for
    * optimization. Success of action is also returned in format
    * [success, foundLogins].
    *
    * - autofillForm denotes if we should fill the form in automatically
    * - ignoreAutocomplete denotes if we should ignore autocomplete=off
    *     attributes
    * - userTriggered is an indication of whether this filling was triggered by
    *     the user
-   * - foundLogins is an array of nsILoginInfo for optimization
+   * - foundLogins is an array of nsILoginInfo
    */
   _fillForm : function (form, autofillForm, ignoreAutocomplete,
                         clobberPassword, userTriggered, foundLogins) {
+    const AUTOFILL_RESULT = {
+      FILLED: 0,
+      NO_PASSWORD_FIELD: 1,
+      PASSWORD_DISABLED_READONLY: 2,
+      NO_LOGINS_FIT: 3,
+      NO_SAVED_LOGINS: 4,
+      EXISTING_PASSWORD: 5,
+      EXISTING_USERNAME: 6,
+      MULTIPLE_LOGINS: 7,
+      NO_AUTOFILL_FORMS: 8,
+      AUTOCOMPLETE_OFF: 9,
+      UNKNOWN_FAILURE: 10,
+    };
+
+    function recordAutofillResult(result) {
+      if (userTriggered) {
+        // Ignore fills as a result of user action.
+        return;
+      }
+      const autofillResultHist = Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT");
+      autofillResultHist.add(result);
+    }
+
+    // Nothing to do if we have no matching logins available.
+    if (foundLogins.length == 0) {
+      // We don't log() here since this is a very common case.
+      recordAutofillResult(AUTOFILL_RESULT.NO_SAVED_LOGINS);
+      return [false, foundLogins];
+    }
+
     // Heuristically determine what the user/pass fields are
     // We do this before checking to see if logins are stored,
     // so that the user isn't prompted for a master password
     // without need.
     var [usernameField, passwordField, ignored] =
         this._getFormFields(form, false);
 
     // Need a valid password field to do anything.
-    if (passwordField == null)
+    if (passwordField == null) {
+      log("not filling form, no password field found");
+      recordAutofillResult(AUTOFILL_RESULT.NO_PASSWORD_FIELD);
       return [false, foundLogins];
+    }
 
     // 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");
+      recordAutofillResult(AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY);
       return [false, foundLogins];
     }
 
     // Discard logins which have username/password values that don't
     // fit into the fields (as specified by the maxlength attribute).
     // The user couldn't enter these values anyway, and it helps
     // with sites that have an extra PIN to be entered (bug 391514)
     var maxUsernameLen = Number.MAX_VALUE;
@@ -615,21 +648,21 @@ var LoginManagerContent = {
       var fit = (l.username.length <= maxUsernameLen &&
                  l.password.length <= maxPasswordLen);
       if (!fit)
         log("Ignored", l.username, "login: won't fit");
 
       return fit;
     }, this);
 
-
-    // Nothing to do if we have no matching logins available.
-    if (logins.length == 0)
+    if (logins.length == 0) {
+      log("form not filled, none of the logins fit in the field");
+      recordAutofillResult(AUTOFILL_RESULT.NO_LOGINS_FIT);
       return [false, foundLogins];
-
+    }
 
     // The reason we didn't end up filling the form, if any.  We include
     // this in the formInfo object we send with the passwordmgr-found-logins
     // notification.  See the _notifyFoundLogins docs for possible values.
     var didntFillReason = null;
 
     // Attach autocomplete stuff to the username field, if we have
     // one. This is normally used to select from multiple accounts,
@@ -637,16 +670,18 @@ var LoginManagerContent = {
     if (usernameField)
       this._formFillService.markAsLoginManagerField(usernameField);
 
     // Don't clobber an existing password.
     if (passwordField.value && !clobberPassword) {
       didntFillReason = "existingPassword";
       this._notifyFoundLogins(didntFillReason, usernameField,
                               passwordField, foundLogins, null);
+      log("form not filled, the password field was already filled");
+      recordAutofillResult(AUTOFILL_RESULT.EXISTING_PASSWORD);
       return [false, foundLogins];
     }
 
     // If the form has an autocomplete=off attribute in play, don't
     // fill in the login automatically. We check this after attaching
     // the autocomplete stuff to the username field, so the user can
     // still manually select a login to be filled in.
     var isFormDisabled = false;
@@ -740,16 +775,38 @@ var LoginManagerContent = {
       didntFillReason = "autocompleteOff";
       Services.obs.notifyObservers(form, "passwordmgr-found-form", didntFillReason);
       log("autocomplete=off but form can be filled; notified observers");
     }
 
     this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
                             foundLogins, selectedLogin);
 
+    if (didFillForm) {
+      recordAutofillResult(AUTOFILL_RESULT.FILLED);
+    } else {
+      let autofillResult = AUTOFILL_RESULT.UNKNOWN_FAILURE;
+      switch (didntFillReason) {
+        // existingPassword is already handled above
+        case "existingUsername":
+          autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
+          break;
+        case "multipleLogins":
+          autofillResult = AUTOFILL_RESULT.MULTIPLE_LOGINS;
+          break;
+        case "noAutofillForms":
+          autofillResult = AUTOFILL_RESULT.NO_AUTOFILL_FORMS;
+          break;
+        case "autocompleteOff":
+          autofillResult = AUTOFILL_RESULT.AUTOCOMPLETE_OFF;
+          break;
+      }
+      recordAutofillResult(autofillResult);
+    }
+
     return [didFillForm, foundLogins];
   },
 
 
   /**
    * Notify observers about an attempt to fill a form that resulted in some
    * saved logins being found for the form.
    *
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7185,16 +7185,22 @@
   "PWMGR_BLOCKLIST_NUM_SITES": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 100,
     "n_buckets" : 10,
     "extended_statistics_ok": true,
     "description": "The number of sites for which the user has explicitly rejected saving logins"
   },
+  "PWMGR_FORM_AUTOFILL_RESULT": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values" : 20,
+    "description": "The result of auto-filling a login form. See http://mzl.la/1Mbs6jL for bucket descriptions."
+  },
   "PWMGR_NUM_PASSWORDS_PER_HOSTNAME": {
     "expires_in_version": "never",
     "kind": "linear",
     "high": 21,
     "n_buckets" : 20,
     "description": "The number of passwords per hostname"
   },
   "PWMGR_NUM_SAVED_PASSWORDS": {