Bug 1124895 - Add password manager usage data to FHR. r=dolske, r=gfritzsche, a=sledru
--- 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,