Bug 1548381 - Generate and cache a password for autocomplete="new-password" password fields. r=sfoster
☠☠ backed out by 2690e619a493 ☠ ☠
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Mon, 20 May 2019 19:54:58 +0000
changeset 474619 fde90ccfb57027a25127edd92eade0bc3e8c853f
parent 474618 426750b88fc2c221b005da3c4d8918216c7c0548
child 474620 e0cf735bdcf5235d4e2e7416d5a07956e7e74671
push id36042
push userdvarga@mozilla.com
push dateTue, 21 May 2019 04:19:40 +0000
treeherdermozilla-central@ca560ff55451 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs1548381
milestone69.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 1548381 - Generate and cache a password for autocomplete="new-password" password fields. r=sfoster Differential Revision: https://phabricator.services.mozilla.com/D31206
browser/base/content/test/static/browser_all_files_referenced.js
toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -142,18 +142,16 @@ var whitelist = [
   {file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
-  // Bug 1548381
-  {file: "resource://gre/modules/PasswordGenerator.jsm"},
   // Bug 1351097
   {file: "resource://gre/modules/accessibility/AccessFu.jsm"},
   // Bug 1356043
   {file: "resource://gre/modules/PerfMeasurement.jsm"},
   // Bug 1356045
   {file: "chrome://global/content/test-ipc.xul"},
   // Bug 1378173 (warning: still used by devtools)
   {file: "resource://gre/modules/Promise.jsm"},
--- a/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
+++ b/toolkit/components/passwordmgr/LoginAutoCompleteResult.jsm
@@ -389,19 +389,17 @@ LoginAutoComplete.prototype = {
       previousResult = {
         searchString: aPreviousResult.searchString,
         logins: aPreviousResult.wrappedJSObject.logins,
       };
     } else {
       previousResult = null;
     }
 
-    let rect = BrowserUtils.getElementBoundingScreenRect(aElement);
     let acLookupPromise = this._autoCompleteLookupPromise =
-      LoginManagerContent._autoCompleteSearchAsync(aSearchString, previousResult,
-                                                   aElement, rect);
+      LoginManagerContent._autoCompleteSearchAsync(aSearchString, previousResult, aElement);
     acLookupPromise.then(completeSearch.bind(this, acLookupPromise)).catch(log.error);
   },
 
   stopSearch() {
     this._autoCompleteLookupPromise = null;
   },
 };
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -340,39 +340,42 @@ var LoginManagerContent = {
                         options };
 
     return this._sendRequest(messageManager, requestData,
                              "PasswordManager:findLogins",
                              messageData);
   },
 
   _autoCompleteSearchAsync(aSearchString, aPreviousResult,
-                           aElement, aRect) {
+                           aElement) {
     let doc = aElement.ownerDocument;
     let form = LoginFormFactory.createFromField(aElement);
     let win = doc.defaultView;
 
     let formOrigin = LoginHelper.getLoginOrigin(doc.documentURI);
     let actionOrigin = LoginHelper.getFormActionOrigin(form);
+    let autocompleteInfo = aElement.getAutocompleteInfo();
 
     let messageManager = win.docShell.messageManager;
 
     let previousResult = aPreviousResult ?
                            { searchString: aPreviousResult.searchString,
                              logins: LoginHelper.loginsToVanillaObjects(aPreviousResult.logins) } :
                            null;
 
     let requestData = {};
-    let messageData = { formOrigin,
-                        actionOrigin,
-                        searchString: aSearchString,
-                        previousResult,
-                        rect: aRect,
-                        isSecure: InsecurePasswordUtils.isFormSecure(form),
-                        isPasswordField: aElement.type == "password",
+    let messageData = {
+      autocompleteInfo,
+      browsingContextId: win.docShell.browsingContext.id,
+      formOrigin,
+      actionOrigin,
+      searchString: aSearchString,
+      previousResult,
+      isSecure: InsecurePasswordUtils.isFormSecure(form),
+      isPasswordField: aElement.type == "password",
     };
 
     if (LoginHelper.showAutoCompleteFooter) {
       messageManager.addMessageListener("FormAutoComplete:PopupOpened", this);
       messageManager.addMessageListener("FormAutoComplete:PopupClosed", this);
     }
 
     return this._sendRequest(messageManager, requestData,
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -10,28 +10,39 @@ const {Services} = ChromeUtils.import("r
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 ChromeUtils.defineModuleGetter(this, "AutoCompletePopup",
                                "resource://gre/modules/AutoCompletePopup.jsm");
 ChromeUtils.defineModuleGetter(this, "DeferredTask",
                                "resource://gre/modules/DeferredTask.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginHelper",
                                "resource://gre/modules/LoginHelper.jsm");
+ChromeUtils.defineModuleGetter(this, "PasswordGenerator",
+                               "resource://gre/modules/PasswordGenerator.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginManagerParent");
   return logger.log.bind(logger);
 });
 
 var EXPORTED_SYMBOLS = [ "LoginManagerParent" ];
 
 var LoginManagerParent = {
   /**
+   * A map of a principal's origin (including suffixes) to a generated password string so that we
+   * can offer the same password later (e.g. in a confirmation field).
+   *
+   * We don't currently evict from this cache so entries should last until the end of the browser
+   * session. That may change later but for now a typical session would max out at a few entries.
+   */
+  _generatedPasswordsByPrincipalOrigin: new Map(),
+
+  /**
    * Reference to the default LoginRecipesParent (instead of the initialization promise) for
    * synchronous access. This is a temporary hack and new consumers should yield on
    * recipeParentPromise instead.
    *
    * @type LoginRecipesParent
    * @deprecated
    */
   _recipeManager: null,
@@ -239,19 +250,26 @@ var LoginManagerParent = {
     var jsLogins = LoginHelper.loginsToVanillaObjects(logins);
     target.sendAsyncMessage("PasswordManager:loginsFound", {
       requestId,
       logins: jsLogins,
       recipes,
     });
   },
 
-  doAutocompleteSearch({ formOrigin, actionOrigin,
-                         searchString, previousResult,
-                         rect, requestId, isSecure, isPasswordField,
+  doAutocompleteSearch({
+    autocompleteInfo,
+    browsingContextId,
+    formOrigin,
+    actionOrigin,
+    searchString,
+    previousResult,
+    requestId,
+    isSecure,
+    isPasswordField,
   }, target) {
     // Note: previousResult is a regular object, not an
     // nsIAutoCompleteResult.
 
     // Cancel if we unsuccessfully prompted for the master password too recently.
     if (!Services.logins.isLoggedIn) {
       let timeDiff = Date.now() - this._lastMPLoginCancelled;
       if (timeDiff < this._repromptTimeout) {
@@ -283,32 +301,59 @@ var LoginManagerParent = {
       logins = this._searchAndDedupeLogins(formOrigin, actionOrigin, {looseActionOriginMatch: true});
     }
 
     let matchingLogins = logins.filter(function(fullMatch) {
       let match = fullMatch.username;
 
       // Remove results that are too short, or have different prefix.
       // Also don't offer empty usernames as possible results except
-      // for password field.
+      // for on password fields.
       if (isPasswordField) {
         return true;
       }
       return match && match.toLowerCase().startsWith(searchStringLower);
     });
 
+    let generatedPassword = null;
+    if (isPasswordField && autocompleteInfo.fieldName == "new-password") {
+      generatedPassword = this.getGeneratedPassword(browsingContextId);
+    }
+
     // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
     // doesn't support structured cloning.
     var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
     target.messageManager.sendAsyncMessage("PasswordManager:loginsAutoCompleted", {
       requestId,
+      generatedPassword,
       logins: jsLogins,
     });
   },
 
+  getGeneratedPassword(browsingContextId) {
+    if (!LoginHelper.enabled || !LoginHelper.generationAvailable || !LoginHelper.generationEnabled) {
+      return null;
+    }
+
+    let browsingContext = BrowsingContext.get(browsingContextId);
+    if (!browsingContext) {
+      return null;
+    }
+    let framePrincipalOrigin = browsingContext.currentWindowGlobal.documentPrincipal.origin;
+    // Use the same password if we already generated one for this origin so that it doesn't change
+    // with each search/keystroke and the user can easily re-enter a password in a confirmation field.
+    let generatedPW = this._generatedPasswordsByPrincipalOrigin.get(framePrincipalOrigin);
+    if (generatedPW) {
+      return generatedPW;
+    }
+    generatedPW = PasswordGenerator.generatePassword();
+    this._generatedPasswordsByPrincipalOrigin.set(framePrincipalOrigin, generatedPW);
+    return generatedPW;
+  },
+
   onFormSubmit({hostname, formSubmitURL, autoFilledLoginGuid,
                 usernameField, newPasswordField,
                 oldPasswordField, openerTopWindowID,
                 dismissedPrompt, target}) {
     function getPrompter() {
       var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
                         createInstance(Ci.nsILoginManagerPrompter);
       prompterSvc.init(target.ownerGlobal);