Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Mon, 22 Apr 2019 12:41:08 +0300
changeset 470330 b783cd5203ea589bb7505852e5108ed142d2d37a
parent 470327 43c7c3f10a71a5bede2282db7dcd80d674cf237d (current diff)
parent 470329 f8541439e10186980ecea712820310b08144ec96 (diff)
child 470331 34059c6188e54415a97125be22b06040dbcc2f93
child 470339 355f26707b354faec37e740b99950e70ab3221fc
child 470374 ab1da7fa2ad0aa1c9d6d0a9e2f63e6492cd1b712
push id35899
push usershindli@mozilla.com
push dateMon, 22 Apr 2019 09:42:40 +0000
treeherdermozilla-central@b783cd5203ea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
b783cd5203ea / 68.0a1 / 20190422094240 / files
nightly linux64
b783cd5203ea / 68.0a1 / 20190422094240 / files
nightly mac
b783cd5203ea / 68.0a1 / 20190422094240 / files
nightly win32
b783cd5203ea / 68.0a1 / 20190422094240 / files
nightly win64
b783cd5203ea / 68.0a1 / 20190422094240 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/mobile/android/components/LoginManagerPrompter.js
+++ b/mobile/android/components/LoginManagerPrompter.js
@@ -102,36 +102,38 @@ LoginManagerPrompter.prototype = {
   // setting this attribute is ignored because Android does not consider
   // opener windows when displaying login notifications
   set opener(aOpener) { },
 
   /*
    * promptToSavePassword
    *
    */
-  promptToSavePassword: function(aLogin) {
-    this._showSaveLoginNotification(aLogin);
+  promptToSavePassword: function(aLogin, dismissed) {
+    this._showSaveLoginNotification(aLogin, dismissed);
       Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED);
     Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
   },
 
   /*
    * _showLoginNotification
    *
    * Displays a notification doorhanger.
    * @param aBody
    *        String message to be displayed in the doorhanger
    * @param aButtons
    *        Buttons to display with the doorhanger
    * @param aUsername
    *        Username string used in creating a doorhanger action
    * @param aPassword
    *        Password string used in creating a doorhanger action
+   * @param dismissed
+   *        A boolean indicating if a prompt is dismissed by default.
    */
-  _showLoginNotification: function(aBody, aButtons, aUsername, aPassword) {
+  _showLoginNotification: function(aBody, aButtons, aUsername, aPassword, dismissed = false) {
     let actionText = {
       text: aUsername,
       type: "EDIT",
       bundle: { username: aUsername,
       password: aPassword },
     };
 
     // The page we're going to hasn't loaded yet, so we want to persist
@@ -140,31 +142,32 @@ LoginManagerPrompter.prototype = {
     // Sites like Gmail perform a funky redirect dance before you end up
     // at the post-authentication page. I don't see a good way to
     // heuristically determine when to ignore such location changes, so
     // we'll try ignoring location changes based on a time interval.
     let options = {
       persistWhileVisible: true,
       timeout: Date.now() + 10000,
       actionText: actionText,
+      dismissed,
     };
 
     let win = (this._browser && this._browser.contentWindow) || this._window;
     DoorHanger.show(win, aBody, "password", aButtons, options, "LOGIN");
   },
 
   /*
    * _showSaveLoginNotification
    *
    * Displays a notification doorhanger (rather than a popup), to allow the user to
    * save the specified login. This allows the user to see the results of
    * their login, and only save a login which they know worked.
    *
    */
-  _showSaveLoginNotification: function(aLogin) {
+  _showSaveLoginNotification: function(aLogin, dismissed) {
     let brandShortName = this._strBundle.brand.GetStringFromName("brandShortName");
     let notificationText  = this._getLocalizedString("saveLogin", [brandShortName]);
 
     // The callbacks in |buttons| have a closure to access the variables
     // in scope here; set one to |Services.logins| so we can get back to pwmgr
     // without a getService() call.
     var pwmgr = Services.logins;
     let promptHistogram = Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION");
@@ -186,41 +189,41 @@ LoginManagerPrompter.prototype = {
           }
           pwmgr.addLogin(aLogin);
           promptHistogram.add(PROMPT_ADD);
         },
         positive: true,
       },
     ];
 
-    this._showLoginNotification(notificationText, buttons, aLogin.username, aLogin.password);
+    this._showLoginNotification(notificationText, buttons, aLogin.username, aLogin.password, dismissed);
   },
 
   /*
    * promptToChangePassword
    *
    * Called when we think we detect a password change for an existing
    * login, when the form being submitted contains multiple password
    * fields.
    *
    */
-  promptToChangePassword: function(aOldLogin, aNewLogin) {
+  promptToChangePassword: function(aOldLogin, aNewLogin, dismissed) {
     this._showChangeLoginNotification(aOldLogin, aNewLogin.password);
     Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION").add(PROMPT_DISPLAYED);
     let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
     Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);
   },
 
   /*
    * _showChangeLoginNotification
    *
    * Shows the Change Password notification doorhanger.
    *
    */
-  _showChangeLoginNotification: function(aOldLogin, aNewPassword) {
+  _showChangeLoginNotification: function(aOldLogin, aNewPassword, dismissed) {
     var notificationText;
     if (aOldLogin.username) {
       let displayUser = this._sanitizeUsername(aOldLogin.username);
       notificationText  = this._getLocalizedString("updatePassword", [displayUser]);
     } else {
       notificationText  = this._getLocalizedString("updatePasswordNoUser");
     }
 
@@ -242,17 +245,17 @@ LoginManagerPrompter.prototype = {
           self._updateLogin(aOldLogin, password);
 
           promptHistogram.add(PROMPT_UPDATE);
         },
         positive: true,
       },
     ];
 
-    this._showLoginNotification(notificationText, buttons, aOldLogin.username, aNewPassword);
+    this._showLoginNotification(notificationText, buttons, aOldLogin.username, aNewPassword, dismissed);
   },
 
   /*
    * promptToChangePasswordWithUsernames
    *
    * Called when we detect a password change in a form submission, but we
    * don't know which existing login (username) it's for. Asks the user
    * to select a username and confirm the password change.
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -16,16 +16,17 @@ var EXPORTED_SYMBOLS = ["LoginManagerCon
 const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
 const AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS = 400;
 const AUTOFILL_STATE = "-moz-autofill";
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 const {PromiseUtils} = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
+const {CreditCard} = ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
 
 ChromeUtils.defineModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
 ChromeUtils.defineModuleGetter(this, "FormLikeFactory",
                                "resource://gre/modules/FormLikeFactory.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginFormFactory",
                                "resource://gre/modules/LoginFormFactory.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginRecipesContent",
                                "resource://gre/modules/LoginRecipes.jsm");
@@ -1154,25 +1155,37 @@ var LoginManagerContent = {
     });
 
     // Make sure to pass the opener's top ID in case it was in a frame.
     let openerTopWindowID = null;
     if (win.opener) {
       openerTopWindowID = win.opener.top.windowUtils.outerWindowID;
     }
 
+    // Dismiss prompt if the username field is a credit card number AND
+    // if the password field is a three digit number. Also dismiss prompt if
+    // the password is a credit card number and the password field has attribute
+    // autocomplete="cc-number".
+    let dismissedPrompt = false;
+    let newPasswordFieldValue = newPasswordField.value;
+    if ((CreditCard.isValidNumber(usernameValue) && newPasswordFieldValue.trim().match(/^[0-9]{3}$/)) ||
+        (CreditCard.isValidNumber(newPasswordFieldValue) && newPasswordField.getAutocompleteInfo().fieldName == "cc-number")) {
+      dismissedPrompt = true;
+    }
+
     let autoFilledLogin = this.stateForDocument(doc).fillsByRootElement.get(form.rootElement);
     messageManager.sendAsyncMessage("PasswordManager:onFormSubmit",
                                     { hostname,
                                       formSubmitURL,
                                       autoFilledLoginGuid: autoFilledLogin && autoFilledLogin.guid,
                                       usernameField: mockUsername,
                                       newPasswordField: mockPassword,
                                       oldPasswordField: mockOldPassword,
                                       openerTopWindowID,
+                                      dismissedPrompt,
                                     });
   },
 
   /** Remove login field highlight when its value is cleared or overwritten.
    */
   _removeFillFieldHighlight(event) {
     let winUtils = event.target.ownerGlobal.windowUtils;
     winUtils.removeManuallyManagedState(event.target, AUTOFILL_STATE);
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -95,16 +95,17 @@ var LoginManagerParent = {
         // TODO Verify msg.target's principals against the formOrigin?
         this.onFormSubmit({hostname: data.hostname,
                            formSubmitURL: data.formSubmitURL,
                            autoFilledLoginGuid: data.autoFilledLoginGuid,
                            usernameField: data.usernameField,
                            newPasswordField: data.newPasswordField,
                            oldPasswordField: data.oldPasswordField,
                            openerTopWindowID: data.openerTopWindowID,
+                           dismissedPrompt: data.dismissedPrompt,
                            target: msg.target});
         break;
       }
 
       case "PasswordManager:insecureLoginFormPresent": {
         this.setHasInsecureLoginForms(msg.target, data.hasInsecureLoginForms);
         break;
       }
@@ -298,17 +299,17 @@ var LoginManagerParent = {
       requestId,
       logins: jsLogins,
     });
   },
 
   onFormSubmit({hostname, formSubmitURL, autoFilledLoginGuid,
                 usernameField, newPasswordField,
                 oldPasswordField, openerTopWindowID,
-                target}) {
+                dismissedPrompt, target}) {
     function getPrompter() {
       var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
                         createInstance(Ci.nsILoginManagerPrompter);
       prompterSvc.init(target.ownerGlobal);
       prompterSvc.browser = target;
 
       for (let win of Services.wm.getEnumerator(null)) {
         let tabbrowser = win.gBrowser;
@@ -382,17 +383,17 @@ var LoginManagerParent = {
           log("(Not prompting to save/change since we have no username and the " +
               "only saved password matches the new password)");
           return;
         }
 
         formLogin.username      = oldLogin.username;
         formLogin.usernameField = oldLogin.usernameField;
 
-        prompter.promptToChangePassword(oldLogin, formLogin);
+        prompter.promptToChangePassword(oldLogin, formLogin, dismissedPrompt);
       } else {
         // Note: It's possible that that we already have the correct u+p saved
         // but since we don't have the username, we don't know if the user is
         // changing a second account to the new password so we ask anyways.
 
         prompter.promptToChangePasswordWithUsernames(
           logins, logins.length, formLogin);
       }
@@ -440,32 +441,31 @@ var LoginManagerParent = {
 
     if (existingLogin) {
       log("Found an existing login matching this form submission");
 
       // Change password if needed.
       if (existingLogin.password != formLogin.password) {
         log("...passwords differ, prompting to change.");
         prompter = getPrompter();
-        prompter.promptToChangePassword(existingLogin, formLogin);
+        prompter.promptToChangePassword(existingLogin, formLogin, dismissedPrompt);
       } else if (!existingLogin.username && formLogin.username) {
         log("...empty username update, prompting to change.");
         prompter = getPrompter();
-        prompter.promptToChangePassword(existingLogin, formLogin);
+        prompter.promptToChangePassword(existingLogin, formLogin, dismissedPrompt);
       } else {
         recordLoginUse(existingLogin);
       }
 
       return;
     }
 
-
     // Prompt user to save login (via dialog or notification bar)
     prompter = getPrompter();
-    prompter.promptToSavePassword(formLogin);
+    prompter.promptToSavePassword(formLogin, dismissedPrompt);
   },
 
   /**
    * Maps all the <browser> elements for tabs in the parent process to the
    * current state used to display tab-specific UI.
    *
    * This mapping is not updated in case a web page is moved to a different
    * chrome window by the swapDocShells method. In this case, it is possible
--- a/toolkit/components/passwordmgr/LoginManagerPrompter.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerPrompter.jsm
@@ -807,22 +807,22 @@ LoginManagerPrompter.prototype = {
   set browser(aBrowser) {
     this._browser = aBrowser;
   },
 
   set openerBrowser(aOpenerBrowser) {
     this._openerBrowser = aOpenerBrowser;
   },
 
-  promptToSavePassword(aLogin) {
+  promptToSavePassword(aLogin, dismissed) {
     this.log("promptToSavePassword");
     var notifyObj = this._getPopupNote();
     if (notifyObj) {
       this._showLoginCaptureDoorhanger(aLogin, "password-save", {
-        dismissed: this._inPrivateBrowsing,
+        dismissed: this._inPrivateBrowsing || dismissed,
       });
       Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
     } else {
       this._showSaveLoginDialog(aLogin);
     }
   },
 
   /**
@@ -1171,47 +1171,53 @@ LoginManagerPrompter.prototype = {
    * Called when we think we detect a password or username change for
    * an existing login, when the form being submitted contains multiple
    * password fields.
    *
    * @param {nsILoginInfo} aOldLogin
    *                       The old login we may want to update.
    * @param {nsILoginInfo} aNewLogin
    *                       The new login from the page form.
+   * @param dismissed
+   *        A boolean indicating if the prompt should be automatically
+   *        dismissed on being shown.
    */
-  promptToChangePassword(aOldLogin, aNewLogin) {
+  promptToChangePassword(aOldLogin, aNewLogin, dismissed) {
     this.log("promptToChangePassword");
     let notifyObj = this._getPopupNote();
 
     if (notifyObj) {
       this._showChangeLoginNotification(notifyObj, aOldLogin,
-                                        aNewLogin);
+                                        aNewLogin, dismissed);
     } else {
       this._showChangeLoginDialog(aOldLogin, aNewLogin);
     }
   },
 
   /**
    * Shows the Change Password popup notification.
    *
    * @param aNotifyObj
    *        A popup notification.
    *
    * @param aOldLogin
    *        The stored login we want to update.
    *
    * @param aNewLogin
    *        The login object with the changes we want to make.
+   * @param dismissed
+   *        A boolean indicating if the prompt should be automatically
+   *        dismissed on being shown.
    */
-  _showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin) {
+  _showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin, dismissed = false) {
     aOldLogin.hostname = aNewLogin.hostname;
     aOldLogin.formSubmitURL = aNewLogin.formSubmitURL;
     aOldLogin.password = aNewLogin.password;
     aOldLogin.username = aNewLogin.username;
-    this._showLoginCaptureDoorhanger(aOldLogin, "password-change");
+    this._showLoginCaptureDoorhanger(aOldLogin, "password-change", {dismissed});
 
     let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
     Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);
   },
 
 
   /**
    * Shows the Change Password dialog.
--- a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
+++ b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl
@@ -44,30 +44,37 @@ interface nsILoginManagerPrompter : nsIS
    */
   attribute Element openerBrowser;
 
   /**
    * Ask the user if they want to save a login (Yes, Never, Not Now)
    *
    * @param aLogin
    *        The login to be saved.
+   * @param dismissed
+   *        A boolean value indicating whether the save logins doorhanger should
+   *        be dismissed automatically when shown.
    */
-  void promptToSavePassword(in nsILoginInfo aLogin);
+  void promptToSavePassword(in nsILoginInfo aLogin, in boolean dismissed);
 
   /**
    * Ask the user if they want to change a login's password or username.
    * If the user consents, modifyLogin() will be called.
    *
    * @param aOldLogin
    *        The existing login (with the old password).
    * @param aNewLogin
    *        The new login.
+   * @param dismissed
+   *        A boolean value indicating whether the save logins doorhanger should
+   *        be dismissed automatically when shown.
    */
   void promptToChangePassword(in nsILoginInfo aOldLogin,
-                              in nsILoginInfo aNewLogin);
+                              in nsILoginInfo aNewLogin,
+                              in boolean dismissed);
 
   /**
    * Ask the user if they want to change the password for one of
    * multiple logins, when the caller can't determine exactly which
    * login should be changed. If the user consents, modifyLogin() will
    * be called.
    *
    * @param logins
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -44,16 +44,17 @@ support-files =
   subtst_notifications_11.html
   subtst_notifications_11_popup.html
 skip-if = os == "linux" # Bug 1312981, bug 1313136
 [browser_context_menu_autocomplete_interaction.js]
 skip-if = verify
 [browser_username_select_dialog.js]
 support-files =
   subtst_notifications_change_p.html
+[browser_doorhanger_dismissed_for_ccnumber.js]
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 skip-if = (os == "linux") || (os == "mac") # Bug 1337606
 [browser_exceptions_dialog.js]
 [browser_focus_before_first_DOMContentLoaded.js]
 support-files =
   file_focus_before_DOMContentLoaded.sjs
 [browser_formless_submit_chrome.js]
@@ -66,17 +67,17 @@ skip-if = verify
 skip-if = os == "win" && os_version == "10.0" && debug # bug 1530935
 [browser_insecurePasswordConsoleWarning.js]
 skip-if = verify
 [browser_master_password_autocomplete.js]
 [browser_notifications.js]
 [browser_notifications_username.js]
 [browser_notifications_password.js]
 [browser_notifications_2.js]
-skip-if = (os == "linux") || (os == "win") || (os == "mac") # Bug 1272849 Main action button disabled state intermittent, Bug 1272849
+skip-if = !verify # Bug 1272849
 [browser_openPasswordManager.js]
 [browser_passwordmgr_editing.js]
 skip-if = os == "linux"
 [browser_context_menu.js]
 [browser_context_menu_iframe.js]
 [browser_passwordmgr_contextmenu.js]
 subsuite = clipboard
 [browser_passwordmgr_fields.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_dismissed_for_ccnumber.js
@@ -0,0 +1,115 @@
+"use strict";
+
+const TEST_HOSTNAME = "https://example.com";
+const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
+
+function getSubmitMessage() {
+  info("getSubmitMessage");
+  return new Promise((resolve, reject) => {
+    Services.mm.addMessageListener("PasswordManager:onFormSubmit", function onFormSubmit() {
+      Services.mm.removeMessageListener("PasswordManager:onFormSubmit", onFormSubmit);
+      resolve();
+    });
+  });
+}
+
+add_task(async function test_doorhanger_dismissal_un() {
+  let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url,
+  }, async function test_un_value_as_ccnumber(browser) {
+    // If the username field has a credit card number and if
+    // the password field is a three digit numberic value,
+    // we automatically dismiss the save logins prompt on submission.
+
+    let processedPromise = getSubmitMessage();
+    ContentTask.spawn(browser, null, async () => {
+      content.document.getElementById("form-basic-username").setUserInput("4111111111111111");
+      content.document.getElementById("form-basic-password").setUserInput("123");
+
+      content.document.getElementById("form-basic-submit").click();
+    });
+    await processedPromise;
+
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    ok(notif.dismissed, "notification popup was automatically dismissed");
+  });
+});
+
+add_task(async function test_doorhanger_dismissal_pw() {
+  let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url,
+  }, async function test_pw_value_as_ccnumber(browser) {
+    // If the password field has a credit card number and if
+    // the password field is also tagged autocomplete="cc-number",
+    // we automatically dismiss the save logins prompt on submission.
+
+    let processedPromise = getSubmitMessage();
+    ContentTask.spawn(browser, null, async () => {
+      content.document.getElementById("form-basic-username").setUserInput("aaa");
+      content.document.getElementById("form-basic-password").setUserInput("4111111111111111");
+      content.document.getElementById("form-basic-password").setAttribute("autocomplete", "cc-number");
+
+      content.document.getElementById("form-basic-submit").click();
+    });
+    await processedPromise;
+
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    ok(notif.dismissed, "notification popup was automatically dismissed");
+  });
+});
+
+add_task(async function test_doorhanger_shown_on_un_with_invalid_ccnumber() {
+  let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url,
+  }, async function test_un_with_invalid_cc_number(browser) {
+    // If the username field has a CC number that is invalid,
+    // we show the doorhanger to save logins like we usually do.
+
+    let processedPromise = getSubmitMessage();
+    ContentTask.spawn(browser, null, async () => {
+      content.document.getElementById("form-basic-username").value = "1234123412341234";
+      content.document.getElementById("form-basic-password").value = "411";
+
+      content.document.getElementById("form-basic-submit").click();
+    });
+    await processedPromise;
+
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    ok(!notif.dismissed, "notification popup was not automatically dismissed");
+  });
+});
+
+add_task(async function test_doorhanger_dismissal_on_change() {
+  let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url,
+  }, async function test_change_in_pw(browser) {
+    let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
+                                                 Ci.nsILoginInfo, "init");
+    let login = new nsLoginInfo("https://example.org", "https://example.org", null,
+                                "4111111111111111", "111", "form-basic-username", "form-basic-password");
+    Services.logins.addLogin(login);
+
+    let processedPromise = getSubmitMessage();
+    ContentTask.spawn(browser, null, async () => {
+      content.document.getElementById("form-basic-password").setUserInput("111");
+      content.document.getElementById("form-basic-submit").click();
+    });
+    await processedPromise;
+
+    let notif = getCaptureDoorhanger("password-save");
+    ok(notif, "got notification popup");
+    ok(notif.dismissed, "notification popup was automatically dismissed");
+  });
+});
+
--- a/toolkit/components/passwordmgr/test/browser/browser_notifications_2.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_notifications_2.js
@@ -30,16 +30,17 @@ add_task(async function test_empty_passw
     await promiseShown;
 
     let notificationElement = PopupNotifications.panel.childNodes[0];
     let passwordTextbox = notificationElement.querySelector("#password-notification-password");
 
     // Synthesize input to empty the field
     passwordTextbox.focus();
     await EventUtils.synthesizeKey("KEY_ArrowRight");
+    await EventUtils.synthesizeKey("KEY_ArrowRight");
     await EventUtils.synthesizeKey("KEY_Backspace");
     await EventUtils.synthesizeKey("KEY_Backspace");
 
     let mainActionButton = notificationElement.button;
     Assert.ok(mainActionButton.disabled, "Main action button is disabled");
   });
 });