Bug 1608902 - Introduce recordPasswordUse login storage method. r=sfoster
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 15 Jan 2020 16:57:29 +0000
changeset 510433 28c632950e5e3b7cf6998569a8b339a5775bead5
parent 510432 e12e7c32c8caf0cce516424d9a6c0c7e5901a51a
child 510434 198f7c0bb5bba88bcec19fffa3bfe38b161cf818
push id37021
push userrmaries@mozilla.com
push dateThu, 16 Jan 2020 09:46:51 +0000
treeherdermozilla-central@7541d616ff87 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs1608902
milestone74.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1608902 - Introduce recordPasswordUse login storage method. r=sfoster Differential Revision: https://phabricator.services.mozilla.com/D59768
toolkit/components/passwordmgr/LoginManager.jsm
toolkit/components/passwordmgr/LoginManagerAuthPrompter.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
toolkit/components/passwordmgr/LoginManagerPrompter.jsm
toolkit/components/passwordmgr/nsILoginManager.idl
toolkit/components/passwordmgr/nsILoginManagerStorage.idl
toolkit/components/passwordmgr/storage-geckoview.js
toolkit/components/passwordmgr/storage-json.js
--- a/toolkit/components/passwordmgr/LoginManager.jsm
+++ b/toolkit/components/passwordmgr/LoginManager.jsm
@@ -381,16 +381,27 @@ LoginManager.prototype = {
     log.debug(
       "Modifying login",
       oldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid
     );
     return this._storage.modifyLogin(oldLogin, newLogin);
   },
 
   /**
+   * Record that the password of a saved login was used (e.g. submitted or copied).
+   */
+  recordPasswordUse(login) {
+    log.debug(
+      "Recording password use",
+      login.QueryInterface(Ci.nsILoginMetaInfo).guid
+    );
+    this._storage.recordPasswordUse(login);
+  },
+
+  /**
    * Get a dump of all stored logins. Used by the login manager UI.
    *
    * @return {nsILoginInfo[]} - If there are no logins, the array is empty.
    */
   getAllLogins() {
     log.debug("Getting a list of all logins");
     return this._storage.getAllLogins();
   },
--- a/toolkit/components/passwordmgr/LoginManagerAuthPrompter.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerAuthPrompter.jsm
@@ -499,17 +499,17 @@ LoginManagerAuthPrompter.prototype = {
       this.log("New login seen for " + realm);
       Services.logins.addLogin(newLogin);
     } else if (aPassword.value != selectedLogin.password) {
       // update password
       this.log("Updating password for  " + realm);
       this._updateLogin(selectedLogin, newLogin);
     } else {
       this.log("Login unchanged, no further action needed.");
-      this._updateLogin(selectedLogin);
+      Services.logins.recordPasswordUse(selectedLogin);
     }
 
     return ok;
   },
 
   /**
    * If a password is found in the database for the password realm, it is
    * returned straight away without displaying a dialog.
@@ -822,17 +822,17 @@ LoginManagerAuthPrompter.prototype = {
         );
         if (notifyObj) {
           this._showChangeLoginNotification(browser, selectedLogin, newLogin);
         } else {
           this._updateLogin(selectedLogin, newLogin);
         }
       } else {
         this.log("Login unchanged, no further action needed.");
-        this._updateLogin(selectedLogin);
+        Services.logins.recordPasswordUse(selectedLogin);
       }
     } catch (e) {
       Cu.reportError("LoginManagerAuthPrompter: Fail2 in promptAuth: " + e);
     }
 
     return ok;
   },
 
@@ -1095,31 +1095,29 @@ LoginManagerAuthPrompter.prototype = {
       aNewLogin,
       "passwordmgr-prompt-change",
       oldGUID
     );
   },
 
   /* ---------- Internal Methods ---------- */
 
-  _updateLogin(login, aNewLogin = null) {
+  _updateLogin(login, aNewLogin) {
     var now = Date.now();
     var propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
       Ci.nsIWritablePropertyBag
     );
-    if (aNewLogin) {
-      propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
-      propBag.setProperty("origin", aNewLogin.origin);
-      propBag.setProperty("password", aNewLogin.password);
-      propBag.setProperty("username", aNewLogin.username);
-      // Explicitly set the password change time here (even though it would
-      // be changed automatically), to ensure that it's exactly the same
-      // value as timeLastUsed.
-      propBag.setProperty("timePasswordChanged", now);
-    }
+    propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
+    propBag.setProperty("origin", aNewLogin.origin);
+    propBag.setProperty("password", aNewLogin.password);
+    propBag.setProperty("username", aNewLogin.username);
+    // Explicitly set the password change time here (even though it would
+    // be changed automatically), to ensure that it's exactly the same
+    // value as timeLastUsed.
+    propBag.setProperty("timePasswordChanged", now);
     propBag.setProperty("timeLastUsed", now);
     propBag.setProperty("timesUsedIncrement", 1);
     Services.logins.modifyLogin(login, propBag);
   },
 
   /**
    * Given a content DOM window, returns the chrome window and browser it's in.
    */
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -579,23 +579,17 @@ class LoginManagerParent extends JSWindo
       dismissedPrompt,
     }
   ) {
     function recordLoginUse(login) {
       if (!browser || PrivateBrowsingUtils.isBrowserPrivate(browser)) {
         // don't record non-interactive use in private browsing
         return;
       }
-      // Update the lastUsed timestamp and increment the use count.
-      let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
-        Ci.nsIWritablePropertyBag
-      );
-      propBag.setProperty("timeLastUsed", Date.now());
-      propBag.setProperty("timesUsedIncrement", 1);
-      Services.logins.modifyLogin(login, propBag);
+      Services.logins.recordPasswordUse(login);
     }
 
     // If password storage is disabled, bail out.
     if (!LoginHelper.storageEnabled) {
       return;
     }
 
     if (!Services.logins.getLoginSavingEnabled(origin)) {
--- a/toolkit/components/passwordmgr/LoginManagerPrompter.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerPrompter.jsm
@@ -345,17 +345,17 @@ class LoginManagerPrompter {
           )
         );
       } else if (
         loginToUpdate.password == login.password &&
         loginToUpdate.username == login.username
       ) {
         // We only want to touch the login's use count and last used time.
         log.debug("persistData: Touch matched login", loginToUpdate.guid);
-        this._updateLogin(loginToUpdate);
+        Services.logins.recordPasswordUse(loginToUpdate);
       } else {
         log.debug("persistData: Update matched login", loginToUpdate.guid);
         this._updateLogin(loginToUpdate, login);
         // notify that this auto-saved login has been merged
         if (loginToRemove && loginToRemove.guid == autoSavedLoginGuid) {
           Services.obs.notifyObservers(
             loginToRemove,
             "passwordmgr-autosaved-login-merged"
@@ -663,31 +663,29 @@ class LoginManagerPrompter {
         aNewLogin.passwordField
       );
       LoginManagerPrompter._updateLogin(selectedLogin, newLoginWithUsername);
     }
   }
 
   /* ---------- Internal Methods ---------- */
 
-  static _updateLogin(login, aNewLogin = null) {
+  static _updateLogin(login, aNewLogin) {
     var now = Date.now();
     var propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
       Ci.nsIWritablePropertyBag
     );
-    if (aNewLogin) {
-      propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
-      propBag.setProperty("origin", aNewLogin.origin);
-      propBag.setProperty("password", aNewLogin.password);
-      propBag.setProperty("username", aNewLogin.username);
-      // Explicitly set the password change time here (even though it would
-      // be changed automatically), to ensure that it's exactly the same
-      // value as timeLastUsed.
-      propBag.setProperty("timePasswordChanged", now);
-    }
+    propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
+    propBag.setProperty("origin", aNewLogin.origin);
+    propBag.setProperty("password", aNewLogin.password);
+    propBag.setProperty("username", aNewLogin.username);
+    // Explicitly set the password change time here (even though it would
+    // be changed automatically), to ensure that it's exactly the same
+    // value as timeLastUsed.
+    propBag.setProperty("timePasswordChanged", now);
     propBag.setProperty("timeLastUsed", now);
     propBag.setProperty("timesUsedIncrement", 1);
     Services.logins.modifyLogin(login, propBag);
   }
 
   /**
    * Can be called as:
    *   _getLocalizedString("key1");
--- a/toolkit/components/passwordmgr/nsILoginManager.idl
+++ b/toolkit/components/passwordmgr/nsILoginManager.idl
@@ -74,16 +74,28 @@ interface nsILoginManager : nsISupports 
    * changed in this manner.
    *
    * If the propertybag contains an item named "timesUsedIncrement", the
    * login's timesUsed property will be incremented by the item's value.
    */
   void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
 
   /**
+   * Record that the password of a saved login was used (e.g. submitted or copied).
+   *
+   * @param nsILoginInfo aLogin
+   *        The login record of the password that was used.
+   *
+   * If only the username was used, this method shouldn't be called as we don't
+   * want to double-count the use if both the username and password are copied.
+   * Copying of the username normally precedes the copying of the password anyways.
+   */
+  void recordPasswordUse(in nsILoginInfo aLogin);
+
+  /**
    * Remove all logins known to login manager.
    *
    * The browser sanitization feature allows the user to clear any stored
    * passwords. This interface allows that to be done without getting each
    * login first (which might require knowing the master password).
    */
   void removeAllLogins();
 
--- a/toolkit/components/passwordmgr/nsILoginManagerStorage.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerStorage.idl
@@ -94,16 +94,29 @@ interface nsILoginManagerStorage : nsISu
    *
    * If the propertybag contains an item named "timesUsedIncrement", the
    * login's timesUsed property will be incremented by the item's value.
    */
   void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
 
 
   /**
+   * Record that the password of a saved login was used (e.g. submitted or copied).
+   *
+   * @param nsILoginInfo aLogin
+   *        The login record of the password that was used.
+   *
+   * If only the username was used, this method shouldn't be called as we don't
+   * want to double-count the use if both the username and password are copied.
+   * Copying of the username normally precedes the copying of the password anyways.
+   */
+  void recordPasswordUse(in nsILoginInfo aLogin);
+
+
+  /**
    * Remove all stored logins.
    *
    * The browser sanitization feature allows the user to clear any stored
    * passwords. This interface allows that to be done without getting each
    * login first (which might require knowing the master password).
    */
   void removeAllLogins();
 
--- a/toolkit/components/passwordmgr/storage-geckoview.js
+++ b/toolkit/components/passwordmgr/storage-geckoview.js
@@ -73,16 +73,20 @@ class LoginManagerStorage_geckoview exte
   removeLogin(login) {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   }
 
   modifyLogin(oldLogin, newLoginData) {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  recordPasswordUse(login) {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   getAllLogins() {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   }
 
   /**
    * Returns an array of all saved logins that can be decrypted.
    *
    * @resolve {nsILoginInfo[]}
--- a/toolkit/components/passwordmgr/storage-json.js
+++ b/toolkit/components/passwordmgr/storage-json.js
@@ -303,16 +303,26 @@ class LoginManagerStorage_json {
         this._store.saveSoon();
         break;
       }
     }
 
     LoginHelper.notifyStorageChanged("modifyLogin", [oldStoredLogin, newLogin]);
   }
 
+  recordPasswordUse(login) {
+    // Update the lastUsed timestamp and increment the use count.
+    let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+      Ci.nsIWritablePropertyBag
+    );
+    propBag.setProperty("timeLastUsed", Date.now());
+    propBag.setProperty("timesUsedIncrement", 1);
+    this.modifyLogin(login, propBag);
+  }
+
   async recordBreachAlertDismissal(loginGUID) {
     this._store.ensureDataReady();
     const dismissedBreachAlertsByLoginGUID = this._store._data
       .dismissedBreachAlertsByLoginGUID;
 
     dismissedBreachAlertsByLoginGUID[loginGUID] = {
       timeBreachAlertDismissed: new Date().getTime(),
     };