Merge mozilla-central to autoland. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Mon, 22 Apr 2019 12:47:02 +0300
changeset 470339 355f26707b354faec37e740b99950e70ab3221fc
parent 470338 e7dc7b90caadb4e441a0b6d506f782c15f221b10 (current diff)
parent 470330 b783cd5203ea589bb7505852e5108ed142d2d37a (diff)
child 470340 61587f94c1729e446879c995386ba60fc31f39ba
push id35901
push usershindli@mozilla.com
push dateMon, 22 Apr 2019 15:47:10 +0000
treeherdermozilla-central@1d1471ae2e7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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
Merge mozilla-central to autoland. a=merge CLOSED TREE
--- 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");
   });
 });