Bug 1124895 - Add password manager usage data to FHR. r=dolske, r=gfritzsche, a=sledru
authorAllison Naaktgeboren <ally@mozilla.com>
Tue, 07 Apr 2015 15:29:00 -0400
changeset 258371 ac9862939f3e
parent 258370 a9d1df7af6fc
child 258372 1dc6d70e9022
push id4654
push userryanvm@gmail.com
push date2015-04-08 19:02 +0000
treeherdermozilla-beta@01cf08a90d44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, gfritzsche, sledru
bugs1124895
milestone38.0
Bug 1124895 - Add password manager usage data to FHR. r=dolske, r=gfritzsche, a=sledru
services/healthreport/docs/dataformat.rst
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
toolkit/components/passwordmgr/nsLoginManagerPrompter.js
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1921,8 +1921,35 @@ Example
 
 ::
 
     "org.mozilla.passwordmgr.passwordmgr": {
       "_v": 1,
       "numSavedPasswords": 5,
       "enabled": 0,
     }
+
+Version 2
+^^^^^^^^^
+
+More detailed measurements of login forms & their behavior
+
+numNewSavedPasswordsInSession
+    Number of passwords saved to the password manager this session.
+
+numSuccessfulFills
+    Number of times the password manager filled in password fields for user this session.
+
+numTotalLoginsEncountered
+    Number of times a login form was encountered by the user in the session.
+
+Example
+^^^^^^^
+
+::
+    "org.mozilla.passwordmgr.passwordmgr": {
+      "_v": 2,
+      "numSavedPasswords": 32,
+      "enabled": 1,
+      "numNewSavedPasswords": 5,
+      "numSuccessfulFills": 11,
+      "totalLoginsEncountered": 23,
+    }
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -230,21 +230,26 @@ var LoginManagerContent = {
   /*
    * onFormPassword
    *
    * Called when an <input type="password"> element is added to the page
    */
   onFormPassword: function (event) {
     if (!event.isTrusted)
       return;
+    let form = event.target;
 
+    let doc = form.ownerDocument;
+    let win = doc.defaultView;
+    let messageManager = messageManagerFromWindow(win);
+
+    messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
     if (!gEnabled)
       return;
 
-    let form = event.target;
     log("onFormPassword for", form.ownerDocument.documentURI);
     this._asyncFindLogins(form, { showMasterPassword: true })
         .then(this.loginsFound.bind(this))
         .then(null, Cu.reportError);
   },
 
   loginsFound: function({ form, loginsFound }) {
     let doc = form.ownerDocument;
@@ -776,16 +781,20 @@ var LoginManagerContent = {
       log("autocomplete=off but form can be filled; notified observers");
     }
 
     this._notifyFoundLogins(didntFillReason, usernameField, passwordField,
                             foundLogins, selectedLogin);
 
     if (didFillForm) {
       recordAutofillResult(AUTOFILL_RESULT.FILLED);
+      let doc = form.ownerDocument;
+      let win = doc.defaultView;
+      let messageManager = messageManagerFromWindow(win);
+      messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
     } else {
       let autofillResult = AUTOFILL_RESULT.UNKNOWN_FAILURE;
       switch (didntFillReason) {
         // existingPassword is already handled above
         case "existingUsername":
           autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
           break;
         case "multipleLogins":
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -53,61 +53,107 @@ function log(...pieces) {
 
 #ifndef ANDROID
 #ifdef MOZ_SERVICES_HEALTHREPORT
 XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
                                   "resource://gre/modules/Metrics.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
+const PasswordsHealthReport = {
+  recordDailyCounter: function(aField) {
+    let reporter = Cc["@mozilla.org/datareporting/service;1"]
+                      .getService()
+                      .wrappedJSObject
+                      .healthReporter;
+    // This can happen if the FHR component of the data reporting service is
+    // disabled. This is controlled by a pref that most will never use.
+    if (!reporter) {
+      return;
+    }
+      reporter.onInit().then(() => reporter.getProvider("org.mozilla.passwordmgr")
+                                           .recordDailyCounter(aField));
+
+  }
+};
+
 this.PasswordsMetricsProvider = function() {
   Metrics.Provider.call(this);
 }
 
 PasswordsMetricsProvider.prototype = Object.freeze({
   __proto__: Metrics.Provider.prototype,
 
   name: "org.mozilla.passwordmgr",
 
   measurementTypes: [
     PasswordsMeasurement1,
+    PasswordsMeasurement2,
   ],
 
-  pullOnly: true,
-
-  collectDailyData: function* () {
+  collectDailyData: function () {
     return this.storage.enqueueTransaction(this._recordDailyPasswordData.bind(this));
   },
 
-  _recordDailyPasswordData: function() {
-    let m = this.getMeasurement(PasswordsMeasurement1.prototype.name,
-                                PasswordsMeasurement1.prototype.version);
+  _recordDailyPasswordData: function *() {
+    let m = this.getMeasurement(PasswordsMeasurement2.prototype.name,
+                                PasswordsMeasurement2.prototype.version);
     let enabled = Services.prefs.getBoolPref("signon.rememberSignons");
     yield m.setDailyLastNumeric("enabled", enabled ? 1 : 0);
 
     let loginsCount = Services.logins.countLogins("", "", "");
     yield m.setDailyLastNumeric("numSavedPasswords", loginsCount);
 
   },
+
+  recordDailyCounter: function(aField) {
+    let m = this.getMeasurement(PasswordsMeasurement2.prototype.name,
+                                PasswordsMeasurement2.prototype.version);
+    if (this.storage.hasFieldFromMeasurement(m.id, aField,
+                                             Metrics.Storage.FIELD_DAILY_COUNTER)) {
+      let fieldID = this.storage.fieldIDFromMeasurement(m.id, aField, Metrics.Storage.FIELD_DAILY_COUNTER);
+      return this.enqueueStorageOperation(() => m.incrementDailyCounter(aField));
+    }
+
+    // Otherwise, we first need to create the field.
+    return this.storage.registerField(m.id, aField, Metrics.Storage.FIELD_DAILY_COUNTER)
+           .then(() => m.incrementDailyCounter(aField));
+  },
 });
 
 function PasswordsMeasurement1() {
   Metrics.Measurement.call(this);
 }
 
 PasswordsMeasurement1.prototype = Object.freeze({
   __proto__: Metrics.Measurement.prototype,
   name: "passwordmgr",
   version: 1,
   fields: {
     enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
     numSavedPasswords: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
   },
 });
 
+function PasswordsMeasurement2() {
+  Metrics.Measurement.call(this);
+}
+PasswordsMeasurement2.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+  name: "passwordmgr",
+  version: 2,
+  fields: {
+    enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
+    numSavedPasswords: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
+    numSuccessfulFills: {type: Metrics.Storage.FIELD_DAILY_COUNTER},
+    numNewSavedPasswordsInSession: {type: Metrics.Storage.FIELD_DAILY_COUNTER},
+    numTotalLoginsEncountered: {type: Metrics.Storage.FIELD_DAILY_COUNTER},
+  },
+});
+
 #endif
 #endif
 
 function prefChanged() {
   gDebug = Services.prefs.getBoolPref("signon.debug");
 }
 
 Services.prefs.addObserver("signon.debug", prefChanged, false);
@@ -115,16 +161,30 @@ prefChanged();
 
 var LoginManagerParent = {
   init: function() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
                .getService(Ci.nsIMessageListenerManager);
     mm.addMessageListener("RemoteLogins:findLogins", this);
     mm.addMessageListener("RemoteLogins:onFormSubmit", this);
     mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
+    mm.addMessageListener("LoginStats:LoginEncountered", this);
+    mm.addMessageListener("LoginStats:LoginFillSuccessful", this);
+    Services.obs.addObserver(this, "LoginStats:NewSavedPassword", false);
+  },
+
+  observe: function (aSubject, aTopic, aData) {
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+    if (aTopic == "LoginStats:NewSavedPassword") {
+      PasswordsHealthReport.recordDailyCounter("numNewSavedPasswordsInSession");
+
+    }
+#endif
+#endif
   },
 
   receiveMessage: function (msg) {
     let data = msg.data;
     switch (msg.name) {
       case "RemoteLogins:findLogins": {
         // TODO Verify msg.target's principals against the formOrigin?
         this.findLogins(data.options.showMasterPassword,
@@ -146,16 +206,34 @@ var LoginManagerParent = {
                           msg.target);
         break;
       }
 
       case "RemoteLogins:autoCompleteLogins": {
         this.doAutocompleteSearch(data, msg.target);
         break;
       }
+
+      case "LoginStats:LoginFillSuccessful": {
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+        PasswordsHealthReport.recordDailyCounter("numSuccessfulFills");
+#endif
+#endif
+        break;
+      }
+
+      case "LoginStats:LoginEncountered": {
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+        PasswordsHealthReport.recordDailyCounter("numTotalLoginsEncountered");
+#endif
+#endif
+        break;
+      }
     }
   },
 
   findLogins: function(showMasterPassword, formOrigin, actionOrigin,
                        requestId, target) {
     if (!showMasterPassword && !Services.logins.isLoggedIn) {
       target.sendAsyncMessage("RemoteLogins:loginsFound",
                               { requestId: requestId, logins: [] });
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -830,16 +830,17 @@ LoginManagerPrompter.prototype = {
     if (aNotifyObj == this._getPopupNote()) {
       // "Remember" button
       var mainAction = {
         label:     rememberButtonText,
         accessKey: rememberButtonAccessKey,
         callback: function(aNotifyObj, aButton) {
           promptHistogram.add(PROMPT_ADD);
           pwmgr.addLogin(aLogin);
+          Services.obs.notifyObservers(null, 'LoginStats:NewSavedPassword', null);
           browser.focus();
         }
       };
 
       var secondaryActions = [
         // "Never for this site" button
         {
           label:     neverButtonText,
@@ -866,16 +867,18 @@ LoginManagerPrompter.prototype = {
       var buttons = [
         // "Remember" button
         {
           label:     rememberButtonText,
           accessKey: rememberButtonAccessKey,
           popup:     null,
           callback: function(aNotifyObj, aButton) {
             pwmgr.addLogin(aLogin);
+            Services.obs.notifyObservers(null, 'LoginStats:NewSavedPassword', null);
+
           }
         },
 
         // "Never for this site" button
         {
           label:     neverButtonText,
           accessKey: neverButtonAccessKey,
           popup:     null,