diff --git a/services/healthreport/docs/dataformat.rst b/services/healthreport/docs/dataformat.rst index 4a5154ffe20..2469e33ee9f 100644 --- a/services/healthreport/docs/dataformat.rst +++ b/services/healthreport/docs/dataformat.rst @@ -1959,3 +1959,30 @@ Example "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, + "numTotalLoginsEncountered": 23, + } diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 46b9c916174..0f296be992b 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -261,11 +261,16 @@ var LoginManagerContent = { 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._getLoginDataFromParent(form, { showMasterPassword: true }) .then(this.loginsFound.bind(this)) @@ -806,6 +811,10 @@ var LoginManagerContent = { } recordAutofillResult(AUTOFILL_RESULT.FILLED); + let doc = form.ownerDocument; + let win = doc.defaultView; + let messageManager = messageManagerFromWindow(win); + messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful"); } finally { Services.obs.notifyObservers(form, "passwordmgr-processed-form", null); } diff --git a/toolkit/components/passwordmgr/LoginManagerParent.jsm b/toolkit/components/passwordmgr/LoginManagerParent.jsm index fd795ea4c6b..dc77677ea32 100644 --- a/toolkit/components/passwordmgr/LoginManagerParent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm @@ -21,6 +21,30 @@ this.EXPORTED_SYMBOLS = [ "LoginManagerParent", "PasswordsMetricsProvider" ]; var gDebug; +#ifndef ANDROID +#ifdef MOZ_SERVICES_HEALTHREPORT +XPCOMUtils.defineLazyModuleGetter(this, "Metrics", + "resource://gre/modules/Metrics.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +function recordFHRDailyCounter(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)); + } + +#endif +#endif + function log(...pieces) { function generateLogMessage(args) { let strings = ['Login Manager (parent):']; @@ -53,10 +77,6 @@ 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"); this.PasswordsMetricsProvider = function() { Metrics.Provider.call(this); @@ -69,17 +89,16 @@ PasswordsMetricsProvider.prototype = Object.freeze({ 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); @@ -87,6 +106,20 @@ PasswordsMetricsProvider.prototype = Object.freeze({ 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.enqueueStorageOperation (() => this.storage.registerField(m.id, aField, + Metrics.Storage.FIELD_DAILY_COUNTER).then(() => m.incrementDailyCounter(aField))); + }, }); function PasswordsMeasurement1() { @@ -103,6 +136,22 @@ PasswordsMeasurement1.prototype = Object.freeze({ }, }); +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 @@ -120,12 +169,27 @@ var LoginManagerParent = { 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); XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => { const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {}); let parent = new LoginRecipesParent(); return parent.initializationPromise; }); + + }, + + observe: function (aSubject, aTopic, aData) { +#ifndef ANDROID +#ifdef MOZ_SERVICES_HEALTHREPORT + if (aTopic == "LoginStats:NewSavedPassword") { + recordFHRDailyCounter("numNewSavedPasswordsInSession"); + + } +#endif +#endif }, receiveMessage: function (msg) { @@ -157,6 +221,24 @@ var LoginManagerParent = { this.doAutocompleteSearch(data, msg.target); break; } + + case "LoginStats:LoginFillSuccessful": { +#ifndef ANDROID +#ifdef MOZ_SERVICES_HEALTHREPORT + recordFHRDailyCounter("numSuccessfulFills"); +#endif +#endif + break; + } + + case "LoginStats:LoginEncountered": { +#ifndef ANDROID +#ifdef MOZ_SERVICES_HEALTHREPORT + recordFHRDailyCounter("numTotalLoginsEncountered"); +#endif +#endif + break; + } } }, diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index 80e1ed1d57c..eb7e724444e 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -896,6 +896,10 @@ LoginManagerPrompter.prototype = { accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey), callback: () => { histogram.add(PROMPT_ADD_OR_UPDATE); + if(histogramName == "PWMGR_PROMPT_REMEMBER_ACTION") + { + Services.obs.notifyObservers(null, 'LoginStats:NewSavedPassword', null); + } readDataFromUI(); persistData(); browser.focus();