summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js')
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js1845
1 files changed, 1845 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js
new file mode 100644
index 0000000000..bbcab81854
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_generated_password.js
@@ -0,0 +1,1845 @@
+/**
+ * Test using the generated passwords produces the right doorhangers/notifications
+ */
+
+/* eslint no-shadow:"off" */
+
+"use strict";
+
+// The origin for the test URIs.
+const TEST_ORIGIN = "https://example.com";
+const FORM_PAGE_PATH =
+ "/browser/toolkit/components/passwordmgr/test/browser/form_basic.html";
+const passwordInputSelector = "#form-basic-password";
+const usernameInputSelector = "#form-basic-username";
+
+requestLongerTimeout(2);
+
+async function task_setup() {
+ Services.logins.removeAllUserFacingLogins();
+ LoginTestUtils.resetGeneratedPasswordsCache();
+ await cleanupPasswordNotifications();
+ await LoginTestUtils.remoteSettings.setupImprovedPasswordRules();
+}
+
+async function setup_withOneLogin(username = "username", password = "pass1") {
+ // Reset to a single, known login
+ await task_setup();
+ let login = await LoginTestUtils.addLogin({ username, password });
+ return login;
+}
+
+async function setup_withNoLogins() {
+ // Reset to a single, known login
+ await task_setup();
+ Assert.equal(
+ Services.logins.getAllLogins().length,
+ 0,
+ "0 logins at the start of the test"
+ );
+}
+
+async function fillGeneratedPasswordFromACPopup(
+ browser,
+ passwordInputSelector
+) {
+ let popup = document.getElementById("PopupAutoComplete");
+ Assert.ok(popup, "Got popup");
+ await openACPopup(popup, browser, passwordInputSelector);
+ await fillGeneratedPasswordFromOpenACPopup(browser, passwordInputSelector);
+}
+
+async function checkPromptContents(
+ anchorElement,
+ browser,
+ expectedPasswordLength = 0
+) {
+ let { panel } = PopupNotifications;
+ Assert.ok(PopupNotifications.isPanelOpen, "Confirm popup is open");
+ let notificationElement = panel.childNodes[0];
+ if (expectedPasswordLength) {
+ info(
+ `Waiting for password value to be ${expectedPasswordLength} chars long`
+ );
+ await BrowserTestUtils.waitForCondition(() => {
+ return (
+ notificationElement.querySelector("#password-notification-password")
+ .value.length == expectedPasswordLength
+ );
+ }, "Wait for nsLoginManagerPrompter writeDataToUI()");
+ }
+
+ return {
+ passwordValue: notificationElement.querySelector(
+ "#password-notification-password"
+ ).value,
+ usernameValue: notificationElement.querySelector(
+ "#password-notification-username"
+ ).value,
+ };
+}
+
+async function verifyGeneratedPasswordWasFilled(
+ browser,
+ passwordInputSelector
+) {
+ await SpecialPowers.spawn(
+ browser,
+ [[passwordInputSelector]],
+ function checkFinalFieldValue(inputSelector) {
+ let { LoginTestUtils: LTU } = ChromeUtils.importESModule(
+ "resource://testing-common/LoginTestUtils.sys.mjs"
+ );
+ let passwordInput = content.document.querySelector(inputSelector);
+ Assert.equal(
+ passwordInput.value.length,
+ LTU.generation.LENGTH,
+ "Password field was filled with generated password"
+ );
+ }
+ );
+}
+
+async function openFormInNewTab(url, formValues, taskFn) {
+ let formFilled = listenForTestNotification("FormProcessed");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ await SimpleTest.promiseFocus(browser.ownerGlobal);
+ await formFilled;
+
+ await SpecialPowers.spawn(
+ browser,
+ [formValues],
+ async function prepareAndCheckForm({
+ password: passwordProps,
+ username: usernameProps,
+ }) {
+ let doc = content.document;
+ // give the form an action so we can know when submit is complete
+ doc.querySelector("form").action = "/";
+
+ let props = passwordProps;
+ if (props) {
+ // We'll reuse the form_basic.html, but ensure we'll get the generated password autocomplete option
+ let field = doc.querySelector(props.selector);
+ if (props.type) {
+ // Change the type from 'password' to something else.
+ field.type = props.type;
+ }
+
+ field.setAttribute("autocomplete", "new-password");
+ if (props.hasOwnProperty("expectedValue")) {
+ Assert.equal(
+ field.value,
+ props.expectedValue,
+ "Check autofilled password value"
+ );
+ }
+ }
+ props = usernameProps;
+ if (props) {
+ let field = doc.querySelector(props.selector);
+ if (props.hasOwnProperty("expectedValue")) {
+ Assert.equal(
+ field.value,
+ props.expectedValue,
+ "Check autofilled username value"
+ );
+ }
+ }
+ }
+ );
+
+ if (formValues.password && formValues.password.setValue !== undefined) {
+ info(
+ "Editing the password, expectedMessage? " +
+ formValues.password.expectedMessage
+ );
+ let messagePromise = formValues.password.expectedMessage
+ ? listenForTestNotification(formValues.password.expectedMessage)
+ : Promise.resolve();
+ await changeContentInputValue(
+ browser,
+ formValues.password.selector,
+ formValues.password.setValue
+ );
+ await messagePromise;
+ info("messagePromise resolved");
+ }
+
+ if (formValues.username && formValues.username.setValue !== undefined) {
+ info(
+ "Editing the username, expectedMessage? " +
+ formValues.username.expectedMessage
+ );
+ let messagePromise = formValues.username.expectedMessage
+ ? listenForTestNotification(formValues.username.expectedMessage)
+ : Promise.resolve();
+ await changeContentInputValue(
+ browser,
+ formValues.username.selector,
+ formValues.username.setValue
+ );
+ await messagePromise;
+ info("messagePromise resolved");
+ }
+
+ await taskFn(browser);
+ await closePopup(
+ browser.ownerDocument.getElementById("confirmation-hint")
+ );
+ }
+ );
+}
+
+async function openAndVerifyDoorhanger(browser, type, expected) {
+ // check a dismissed prompt was shown with extraAttr attribute
+ let notif = getCaptureDoorhanger(type);
+ Assert.ok(notif, `${type} doorhanger was created`);
+ Assert.equal(
+ notif.dismissed,
+ expected.dismissed,
+ "Check notification dismissed property"
+ );
+ Assert.equal(
+ notif.anchorElement.getAttribute("extraAttr"),
+ expected.anchorExtraAttr,
+ "Check icon extraAttr attribute"
+ );
+ let { panel } = PopupNotifications;
+ // if the doorhanged is dimissed, we will open it to check panel contents
+ if (panel.state !== "open") {
+ let promiseShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ if (panel.state !== "showing") {
+ // synthesize click on anchor as this also blurs the form field triggering
+ // a change event
+ EventUtils.synthesizeMouseAtCenter(notif.anchorElement, {});
+ }
+ await promiseShown;
+ }
+ let { passwordValue, usernameValue } = await checkPromptContents(
+ notif.anchorElement,
+ browser,
+ expected.passwordLength
+ );
+ Assert.equal(
+ passwordValue.length,
+ expected.passwordLength || LoginTestUtils.generation.LENGTH,
+ "Doorhanger password field has generated 15-char value"
+ );
+ Assert.equal(
+ usernameValue,
+ expected.usernameValue,
+ "Doorhanger username field was popuplated"
+ );
+ return notif;
+}
+
+async function appendContentInputvalue(browser, selector, str) {
+ await ContentTask.spawn(
+ browser,
+ { selector, str },
+ async function ({ selector, str }) {
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ let input = content.document.querySelector(selector);
+ input.focus();
+ input.select();
+ await EventUtils.synthesizeKey("KEY_ArrowRight", {}, content);
+ let changedPromise = ContentTaskUtils.waitForEvent(input, "change");
+ if (str) {
+ await EventUtils.sendString(str, content);
+ }
+ input.blur();
+ await changedPromise;
+ }
+ );
+ info("Input value changed");
+ await TestUtils.waitForTick();
+}
+
+async function submitForm(browser) {
+ // Submit the form
+ info("Now submit the form");
+ let correctPathNamePromise = BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.querySelector("form").submit();
+ });
+ await correctPathNamePromise;
+ await SpecialPowers.spawn(browser, [], async () => {
+ let win = content;
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ win.location.pathname == "/" && win.document.readyState == "complete"
+ );
+ }, "Wait for form submission load");
+ });
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["signon.generation.available", true],
+ ["signon.generation.enabled", true],
+ ],
+ });
+ // assert that there are no logins
+ let logins = Services.logins.getAllLogins();
+ Assert.equal(logins.length, 0, "There are no logins");
+});
+
+add_task(async function autocomplete_generated_password_auto_saved() {
+ // confirm behavior when filling a generated password via autocomplete
+ // when there are no other logins
+ await setup_withNoLogins();
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: { selector: passwordInputSelector, expectedValue: "" },
+ username: { selector: usernameInputSelector, expectedValue: "" },
+ },
+ async function taskFn(browser) {
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // Let the hint hide itself this first time
+ let forceClosePopup = false;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+ let [{ username, password }] = await storageChangedPromise;
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ // Check properties of the newly auto-saved login
+ Assert.equal(username, "", "Saved login should have no username");
+ Assert.equal(
+ password.length,
+ LoginTestUtils.generation.LENGTH,
+ "Saved login should have generated password"
+ );
+
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+ await promiseHidden;
+
+ // confirm the extraAttr attribute is removed after opening & dismissing the doorhanger
+ Assert.ok(
+ !notif.anchorElement.hasAttribute("extraAttr"),
+ "Check if the extraAttr attribute was removed"
+ );
+ await cleanupDoorhanger(notif);
+
+ storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ let [autoSavedLogin] = Services.logins.getAllLogins();
+ info("waiting for submitForm");
+ await submitForm(browser);
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: autoSavedLogin.timesUsed + 1,
+ username: "",
+ },
+ ]);
+ }
+ );
+});
+
+add_task(
+ async function autocomplete_generated_password_with_confirm_field_auto_saved() {
+ // confirm behavior when filling a generated password via autocomplete
+ // when there are no other logins and the form has a confirm password field
+ const FORM_WITH_CONFIRM_FIELD_PAGE_PATH =
+ "/browser/toolkit/components/passwordmgr/test/browser/form_basic_with_confirm_field.html";
+ const confirmPasswordInputSelector = "#form-basic-confirm-password";
+ await setup_withNoLogins();
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_WITH_CONFIRM_FIELD_PAGE_PATH,
+ {
+ password: { selector: passwordInputSelector, expectedValue: "" },
+ username: { selector: usernameInputSelector, expectedValue: "" },
+ },
+ async function taskFn(browser) {
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // Let the hint hide itself this first time
+ let forceClosePopup = false;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+ let [{ username, password }] = await storageChangedPromise;
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+ await verifyGeneratedPasswordWasFilled(
+ browser,
+ confirmPasswordInputSelector
+ );
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ // Check properties of the newly auto-saved login
+ Assert.equal(username, "", "Saved login should have no username");
+ Assert.equal(
+ password.length,
+ LoginTestUtils.generation.LENGTH,
+ "Saved login should have generated password"
+ );
+
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+ await promiseHidden;
+
+ // confirm the extraAttr attribute is removed after opening & dismissing the doorhanger
+ Assert.ok(
+ !notif.anchorElement.hasAttribute("extraAttr"),
+ "Check if the extraAttr attribute was removed"
+ );
+ await cleanupDoorhanger(notif);
+
+ storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ let [autoSavedLogin] = Services.logins.getAllLogins();
+ info("waiting for submitForm");
+ await submitForm(browser);
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: autoSavedLogin.timesUsed + 1,
+ username: "",
+ },
+ ]);
+ }
+ );
+ }
+);
+
+add_task(async function autocomplete_generated_password_saved_empty_username() {
+ // confirm behavior when filling a generated password via autocomplete
+ // when there is an existing saved login with a "" username
+ await setup_withOneLogin("", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: { selector: usernameInputSelector, expectedValue: "" },
+ },
+ async function taskFn(browser) {
+ let [savedLogin] = Services.logins.getAllLogins();
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+ await waitForDoorhanger(browser, "password-change");
+ info("Waiting to openAndVerifyDoorhanger");
+ await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+ await hideDoorhangerPopup();
+ info("Waiting to verifyGeneratedPasswordWasFilled");
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ info("waiting for submitForm");
+ await submitForm(browser);
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for modifyLogin");
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: savedLogin.timesUsed + 1,
+ username: "",
+ },
+ ]);
+ await cleanupDoorhanger(notif); // cleanup the doorhanger for next test
+ }
+ );
+});
+
+add_task(async function autocomplete_generated_password_saved_username() {
+ // confirm behavior when filling a generated password via autocomplete
+ // into a form with username matching an existing saved login
+ await setup_withOneLogin("user1", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: {
+ selector: usernameInputSelector,
+ expectedValue: "user1",
+ },
+ },
+ async function taskFn(browser) {
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // We don't need to wait to confirm the hint hides itelf every time
+ let forceClosePopup = true;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ // Check properties of the newly auto-saved login
+ let [user1LoginSnapshot, autoSavedLogin] = verifyLogins([
+ {
+ username: "user1",
+ password: "xyzpassword", // user1 is unchanged
+ },
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "user1",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+ await promiseHidden;
+
+ // confirm the extraAttr attribute is removed after opening & dismissing the doorhanger
+ Assert.ok(
+ !notif.anchorElement.hasAttribute("extraAttr"),
+ "Check if the extraAttr attribute was removed"
+ );
+ await cleanupDoorhanger(notif);
+
+ storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ info("waiting for submitForm");
+ await submitForm(browser);
+ promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: user1LoginSnapshot.timesUsed + 1,
+ username: "user1",
+ password: autoSavedLogin.password,
+ },
+ ]);
+ }
+ );
+});
+
+add_task(async function ac_gen_pw_saved_empty_un_stored_non_empty_un_in_form() {
+ // confirm behavior when when the form's username field has a non-empty value
+ // and there is an existing saved login with a "" username
+ await setup_withOneLogin("", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: {
+ selector: usernameInputSelector,
+ expectedValue: "",
+ setValue: "myusername",
+ // with an empty password value, no message is sent for a username change
+ expectedMessage: "",
+ },
+ },
+ async function taskFn(browser) {
+ let [savedLogin] = Services.logins.getAllLogins();
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+ await waitForDoorhanger(browser, "password-save");
+ info("Waiting to openAndVerifyDoorhanger");
+ await openAndVerifyDoorhanger(browser, "password-save", {
+ dismissed: true,
+ anchorExtraAttr: "",
+ usernameValue: "myusername",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+ await hideDoorhangerPopup();
+ info("Waiting to verifyGeneratedPasswordWasFilled");
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ info("waiting for submitForm");
+ await submitForm(browser);
+ let notif = await openAndVerifyDoorhanger(browser, "password-save", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "myusername",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, REMEMBER_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for addLogin");
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: savedLogin.timesUsed,
+ username: "",
+ password: "xyzpassword",
+ },
+ {
+ timesUsed: 1,
+ username: "myusername",
+ },
+ ]);
+ await cleanupDoorhanger(notif); // cleanup the doorhanger for next test
+ }
+ );
+});
+
+add_task(async function contextfill_generated_password_saved_empty_username() {
+ // confirm behavior when filling a generated password via context menu
+ // when there is an existing saved login with a "" username
+ await setup_withOneLogin("", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: { selector: usernameInputSelector, expectedValue: "" },
+ },
+ async function taskFn(browser) {
+ let [savedLogin] = Services.logins.getAllLogins();
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ await doFillGeneratedPasswordContextMenuItem(
+ browser,
+ passwordInputSelector
+ );
+ await waitForDoorhanger(browser, "password-change");
+ info("Waiting to openAndVerifyDoorhanger");
+ await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+ await hideDoorhangerPopup();
+ info("Waiting to verifyGeneratedPasswordWasFilled");
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ info("waiting for submitForm");
+ await submitForm(browser);
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for modifyLogin");
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: savedLogin.timesUsed + 1,
+ username: "",
+ },
+ ]);
+ await cleanupDoorhanger(notif); // cleanup the doorhanger for next test
+ }
+ );
+});
+
+async function autocomplete_generated_password_edited_no_auto_save(
+ passwordType = "password"
+) {
+ // confirm behavior when filling a generated password via autocomplete
+ // when there is an existing saved login with a "" username and then editing
+ // the password and autocompleting again.
+ await setup_withOneLogin("", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ type: passwordType,
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: { selector: usernameInputSelector, expectedValue: "" },
+ },
+ async function taskFn(browser) {
+ let [savedLogin] = Services.logins.getAllLogins();
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+ info(
+ "Filled generated password, waiting for dismissed password-change doorhanger"
+ );
+ await waitForDoorhanger(browser, "password-change");
+ info("Waiting to openAndVerifyDoorhanger");
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting to verifyGeneratedPasswordWasFilled");
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ await BrowserTestUtils.sendChar("!", browser);
+ await BrowserTestUtils.sendChar("@", browser);
+ await BrowserTestUtils.synthesizeKey("KEY_Tab", undefined, browser);
+
+ await waitForDoorhanger(browser, "password-change");
+ info("Waiting to openAndVerifyDoorhanger");
+ notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH + 2,
+ });
+
+ promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+ await promiseHidden;
+
+ verifyLogins([
+ {
+ timesUsed: savedLogin.timesUsed,
+ username: "",
+ password: "xyzpassword",
+ },
+ ]);
+
+ info("waiting for submitForm");
+ await submitForm(browser);
+ notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH + 2,
+ });
+
+ promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for modifyLogin");
+ await storageChangedPromise;
+ verifyLogins([
+ {
+ timesUsed: savedLogin.timesUsed + 1,
+ username: "",
+ },
+ ]);
+ await cleanupDoorhanger(notif); // cleanup the doorhanger for next test
+ }
+ );
+
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().clear();
+}
+
+add_task(autocomplete_generated_password_edited_no_auto_save);
+
+add_task(
+ async function autocomplete_generated_password_edited_no_auto_save_type_text() {
+ await autocomplete_generated_password_edited_no_auto_save("text");
+ }
+);
+
+add_task(async function contextmenu_fill_generated_password_and_set_username() {
+ // test when filling with a generated password and editing the username in the form
+ // * the prompt should display the form's username
+ // * the auto-saved login should have "" for username
+ // * confirming the prompt should edit the "" login and add the username
+ await setup_withOneLogin("olduser", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: {
+ selector: usernameInputSelector,
+ expectedValue: "olduser",
+ setValue: "differentuser",
+ // with an empty password value, no message is sent for a username change
+ expectedMessage: "",
+ },
+ },
+ async function taskFn(browser) {
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ await SpecialPowers.spawn(
+ browser,
+ [[passwordInputSelector, usernameInputSelector]],
+ function checkEmptyPasswordField([passwordSelector, usernameSelector]) {
+ Assert.equal(
+ content.document.querySelector(passwordSelector).value,
+ "",
+ "Password field is empty"
+ );
+ }
+ );
+
+ // Let the hint hide itself this first time
+ let forceClosePopup = false;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ info("waiting to fill generated password using context menu");
+ await doFillGeneratedPasswordContextMenuItem(
+ browser,
+ passwordInputSelector
+ );
+
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+ info("waiting for dismissed password-change notification");
+ await waitForDoorhanger(browser, "password-change");
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+
+ // Check properties of the newly auto-saved login
+ verifyLogins([
+ null, // ignore the first one
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+
+ info("Waiting to openAndVerifyDoorhanger");
+ await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "differentuser",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+ await hideDoorhangerPopup();
+ info("Waiting to verifyGeneratedPasswordWasFilled");
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ info("waiting for submitForm");
+ await submitForm(browser);
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "differentuser",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+
+ storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for modifyLogin");
+ await storageChangedPromise;
+ verifyLogins([
+ null,
+ {
+ username: "differentuser",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ timesUsed: 2,
+ },
+ ]);
+ await cleanupDoorhanger(notif); // cleanup the doorhanger for next test
+ }
+ );
+});
+
+add_task(async function contextmenu_password_change_form_without_username() {
+ // test doorhanger behavior when a generated password is filled into a change-password
+ // form with no username
+ await setup_withOneLogin("user1", "xyzpassword");
+ await LoginTestUtils.addLogin({ username: "username2", password: "pass2" });
+ const passwordInputSelector = "#newpass";
+
+ const CHANGE_FORM_PATH =
+ "/browser/toolkit/components/passwordmgr/test/browser/form_password_change.html";
+ await openFormInNewTab(
+ TEST_ORIGIN + CHANGE_FORM_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "",
+ },
+ },
+ async function taskFn(browser) {
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+
+ // We don't need to wait to confirm the hint hides itelf every time
+ let forceClosePopup = true;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ // Make the 2nd field use a generated password
+ info("Using contextmenu to fill with a generated password");
+ await doFillGeneratedPasswordContextMenuItem(
+ browser,
+ passwordInputSelector
+ );
+
+ info("waiting for dismissed password-change notification");
+ await waitForDoorhanger(browser, "password-change");
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+ // Check properties of the newly auto-saved login
+ verifyLogins([
+ null, // ignore the first one
+ null, // ignore the 2nd one
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+
+ info("Waiting to openAndVerifyDoorhanger");
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ });
+ // remove notification so we can unambiguously check no new notification gets created later
+ await cleanupDoorhanger(notif);
+
+ info("Waiting to verifyGeneratedPasswordWasFilled");
+ await verifyGeneratedPasswordWasFilled(browser, passwordInputSelector);
+
+ storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+ let { timeLastUsed } = Services.logins.getAllLogins()[2];
+
+ info("waiting for submitForm");
+ await submitForm(browser);
+
+ info("Waiting for modifyLogin");
+ await storageChangedPromise;
+ verifyLogins([
+ null, // ignore the first one
+ null, // ignore the 2nd one
+ {
+ timesUsed: 2,
+ usedSince: timeLastUsed,
+ },
+ ]);
+ // Check no new doorhanger was shown
+ notif = getCaptureDoorhanger("password-change");
+ Assert.ok(!notif, "No new doorhanger should be shown");
+ await cleanupDoorhanger(); // cleanup for next test
+ }
+ );
+});
+
+add_task(
+ async function autosaved_login_updated_to_existing_login_via_doorhanger() {
+ // test when filling with a generated password and editing the username in the
+ // doorhanger to match an existing login:
+ // * the matching login should be updated
+ // * the auto-saved login should be deleted
+ // * the metadata for the matching login should be updated
+ // * the by-origin cache for the password should point at the updated login
+ await setup_withOneLogin("user1", "xyzpassword");
+ await LoginTestUtils.addLogin({
+ username: "user2",
+ password: "abcpassword",
+ });
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "",
+ },
+ username: {
+ selector: usernameInputSelector,
+ expectedValue: "",
+ },
+ },
+ async function taskFn(browser) {
+ await SimpleTest.promiseFocus(browser.ownerGlobal);
+
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // We don't need to wait to confirm the hint hides itelf every time
+ let forceClosePopup = true;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ info("waiting to fill generated password using context menu");
+ await doFillGeneratedPasswordContextMenuItem(
+ browser,
+ passwordInputSelector
+ );
+
+ info("waiting for dismissed password-change notification");
+ await waitForDoorhanger(browser, "password-change");
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+ info("addLogin promise resolved");
+ // Check properties of the newly auto-saved login
+ let [user1LoginSnapshot, unused, autoSavedLogin] = verifyLogins([
+ null, // ignore the first one
+ null, // ignore the 2nd one
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+ info("user1LoginSnapshot, guid: " + user1LoginSnapshot.guid);
+ info("unused, guid: " + unused.guid);
+ info("autoSavedLogin, guid: " + autoSavedLogin.guid);
+
+ info("verifyLogins ok");
+ let passwordCacheEntry =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://example.com"
+ );
+
+ Assert.ok(
+ passwordCacheEntry,
+ "Got the cached generated password entry for https://example.com"
+ );
+ Assert.equal(
+ passwordCacheEntry.value,
+ autoSavedLogin.password,
+ "Cached password matches the auto-saved login password"
+ );
+ Assert.equal(
+ passwordCacheEntry.storageGUID,
+ autoSavedLogin.guid,
+ "Cached password guid matches the auto-saved login guid"
+ );
+
+ info("Waiting to openAndVerifyDoorhanger");
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ password: autoSavedLogin.password,
+ });
+ Assert.ok(notif, "Got password-change notification");
+
+ info("Calling updateDoorhangerInputValues");
+ await updateDoorhangerInputValues({
+ username: "user1",
+ });
+ info("doorhanger inputs updated");
+
+ let loginModifiedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (subject, data) => {
+ if (data == "modifyLogin") {
+ info("passwordmgr-storage-changed, action: " + data);
+ info("subject: " + JSON.stringify(subject));
+ return true;
+ }
+ return false;
+ }
+ );
+ let loginRemovedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (subject, data) => {
+ if (data == "removeLogin") {
+ info("passwordmgr-storage-changed, action: " + data);
+ info("subject: " + JSON.stringify(subject));
+ return true;
+ }
+ return false;
+ }
+ );
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ info("clicking change button");
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for modifyLogin promise");
+ await loginModifiedPromise;
+
+ info("Waiting for removeLogin promise");
+ await loginRemovedPromise;
+
+ info("storage-change promises resolved");
+ // Check the auto-saved login was removed and the original login updated
+ verifyLogins([
+ {
+ username: "user1",
+ password: autoSavedLogin.password,
+ timeCreated: user1LoginSnapshot.timeCreated,
+ timeLastUsed: user1LoginSnapshot.timeLastUsed,
+ passwordChangedSince: autoSavedLogin.timePasswordChanged,
+ },
+ null, // ignore user2
+ ]);
+
+ // Check we have no notifications at this point
+ Assert.ok(!PopupNotifications.isPanelOpen, "No doorhanger is open");
+ Assert.ok(
+ !PopupNotifications.getNotification("password", browser),
+ "No notifications"
+ );
+
+ // make sure the cache entry is unchanged with the removal of the auto-saved login
+ Assert.equal(
+ autoSavedLogin.password,
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://example.com"
+ ).value,
+ "Generated password cache entry has the expected password value"
+ );
+ }
+ );
+ }
+);
+
+add_task(async function autosaved_login_updated_to_existing_login_onsubmit() {
+ // test when selecting auto-saved generated password in a form filled with an
+ // existing login and submitting the form:
+ // * the matching login should be updated
+ // * the auto-saved login should be deleted
+ // * the metadata for the matching login should be updated
+ // * the by-origin cache for the password should point at the updated login
+
+ // clear both fields which should be autofilled with our single login
+ await setup_withOneLogin("user1", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: {
+ selector: usernameInputSelector,
+ expectedValue: "user1",
+ setValue: "",
+ // with an empty password value, no message is sent for a username change
+ expectedMessage: "",
+ },
+ },
+ async function taskFn(browser) {
+ await SimpleTest.promiseFocus(browser.ownerGlobal);
+
+ // first, create an auto-saved login with generated password
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // We don't need to wait to confirm the hint hides itelf every time
+ let forceClosePopup = true;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ info("waiting to fill generated password using context menu");
+ await doFillGeneratedPasswordContextMenuItem(
+ browser,
+ passwordInputSelector
+ );
+
+ info("waiting for dismissed password-change notification");
+ await waitForDoorhanger(browser, "password-change");
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+ info("addLogin promise resolved");
+ // Check properties of the newly auto-saved login
+ let [user1LoginSnapshot, autoSavedLogin] = verifyLogins([
+ null, // ignore the first one
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+ info("user1LoginSnapshot, guid: " + user1LoginSnapshot.guid);
+ info("autoSavedLogin, guid: " + autoSavedLogin.guid);
+
+ info("verifyLogins ok");
+ let passwordCacheEntry =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://example.com"
+ );
+
+ Assert.ok(
+ passwordCacheEntry,
+ "Got the cached generated password entry for https://example.com"
+ );
+ Assert.equal(
+ passwordCacheEntry.value,
+ autoSavedLogin.password,
+ "Cached password matches the auto-saved login password"
+ );
+ Assert.equal(
+ passwordCacheEntry.storageGUID,
+ autoSavedLogin.guid,
+ "Cached password guid matches the auto-saved login guid"
+ );
+
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ password: autoSavedLogin.password,
+ });
+ await cleanupDoorhanger(notif);
+
+ // now update and submit the form with the user1 username and the generated password
+ info(`submitting form`);
+ let submitResults = await submitFormAndGetResults(
+ browser,
+ "formsubmit.sjs",
+ {
+ "#form-basic-username": "user1",
+ }
+ );
+ Assert.equal(
+ submitResults.username,
+ "user1",
+ "Form submitted with expected username"
+ );
+ Assert.equal(
+ submitResults.password,
+ autoSavedLogin.password,
+ "Form submitted with expected password"
+ );
+ info(
+ `form was submitted, got username/password ${submitResults.username}/${submitResults.password}`
+ );
+
+ await waitForDoorhanger(browser, "password-change");
+ notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "user1",
+ password: autoSavedLogin.password,
+ });
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ let loginModifiedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => {
+ if (data == "modifyLogin") {
+ info("passwordmgr-storage-changed, action: " + data);
+ info("subject: " + JSON.stringify(_));
+ return true;
+ }
+ return false;
+ }
+ );
+ let loginRemovedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => {
+ if (data == "removeLogin") {
+ info("passwordmgr-storage-changed, action: " + data);
+ info("subject: " + JSON.stringify(_));
+ return true;
+ }
+ return false;
+ }
+ );
+
+ info("clicking change button");
+ clickDoorhangerButton(notif, CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("Waiting for modifyLogin promise");
+ await loginModifiedPromise;
+
+ info("Waiting for removeLogin promise");
+ await loginRemovedPromise;
+
+ info("storage-change promises resolved");
+ // Check the auto-saved login was removed and the original login updated
+ verifyLogins([
+ {
+ username: "user1",
+ password: autoSavedLogin.password,
+ timeCreated: user1LoginSnapshot.timeCreated,
+ timeLastUsed: user1LoginSnapshot.timeLastUsed,
+ passwordChangedSince: autoSavedLogin.timePasswordChanged,
+ },
+ ]);
+
+ // Check we have no notifications at this point
+ Assert.ok(!PopupNotifications.isPanelOpen, "No doorhanger is open");
+ Assert.ok(
+ !PopupNotifications.getNotification("password", browser),
+ "No notifications"
+ );
+
+ // make sure the cache entry is unchanged with the removal of the auto-saved login
+ Assert.equal(
+ autoSavedLogin.password,
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://example.com"
+ ).value,
+ "Generated password cache entry has the expected password value"
+ );
+ }
+ );
+});
+
+add_task(async function form_change_from_autosaved_login_to_existing_login() {
+ // test when changing from a generated password in a form to an existing saved login
+ // * the auto-saved login should not be deleted
+ // * the metadata for the matching login should be updated
+ // * the by-origin cache for the password should point at the autosaved login
+
+ await setup_withOneLogin("user1", "xyzpassword");
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {
+ password: {
+ selector: passwordInputSelector,
+ expectedValue: "xyzpassword",
+ setValue: "",
+ expectedMessage: "PasswordEditedOrGenerated",
+ },
+ username: {
+ selector: usernameInputSelector,
+ expectedValue: "user1",
+ setValue: "",
+ // with an empty password value, no message is sent for a username change
+ expectedMessage: "",
+ },
+ },
+ async function taskFn(browser) {
+ await SimpleTest.promiseFocus(browser);
+
+ // first, create an auto-saved login with generated password
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // We don't need to wait to confirm the hint hides itelf every time
+ let forceClosePopup = true;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ info("Filling generated password from AC menu");
+ await fillGeneratedPasswordFromACPopup(browser, passwordInputSelector);
+
+ info("waiting for dismissed password-change notification");
+ await waitForDoorhanger(browser, "password-change");
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+ info("addLogin promise resolved");
+ // Check properties of the newly auto-saved login
+ let [user1LoginSnapshot, autoSavedLogin] = verifyLogins([
+ null, // ignore the first one
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+ info("user1LoginSnapshot, guid: " + user1LoginSnapshot.guid);
+ info("autoSavedLogin, guid: " + autoSavedLogin.guid);
+
+ info("verifyLogins ok");
+ let passwordCacheEntry =
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://example.com"
+ );
+
+ Assert.ok(
+ passwordCacheEntry,
+ "Got the cached generated password entry for https://example.com"
+ );
+ Assert.equal(
+ passwordCacheEntry.value,
+ autoSavedLogin.password,
+ "Cached password matches the auto-saved login password"
+ );
+ Assert.equal(
+ passwordCacheEntry.storageGUID,
+ autoSavedLogin.guid,
+ "Cached password guid matches the auto-saved login guid"
+ );
+
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ password: autoSavedLogin.password,
+ });
+
+ // close but don't remove the doorhanger, we want to ensure it is updated/replaced on further form edits
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ let PN = notif.owner;
+ PN.panel.hidePopup();
+ await promiseHidden;
+ await TestUtils.waitForTick();
+
+ // now update the form with the user1 username and password
+ info(`updating form`);
+ let passwordEditedMessages = listenForTestNotification(
+ "PasswordEditedOrGenerated",
+ 2
+ );
+ let passwordChangeDoorhangerPromise = waitForDoorhanger(
+ browser,
+ "password-change"
+ );
+ let hintDidShow = false;
+ let hintPromiseShown = BrowserTestUtils.waitForPopupEvent(
+ document.getElementById("confirmation-hint"),
+ "shown"
+ );
+ hintPromiseShown.then(() => (hintDidShow = true));
+
+ info("Entering username and password for the previously saved login");
+
+ await changeContentFormValues(browser, {
+ [passwordInputSelector]: user1LoginSnapshot.password,
+ [usernameInputSelector]: user1LoginSnapshot.username,
+ });
+ info(
+ "form edited, waiting for test notification of PasswordEditedOrGenerated"
+ );
+
+ await passwordEditedMessages;
+ info("Resolved listenForTestNotification promise");
+
+ await passwordChangeDoorhangerPromise;
+ // wait to ensure there's no confirmation hint
+ try {
+ await TestUtils.waitForCondition(
+ () => {
+ return hintDidShow;
+ },
+ `Waiting for confirmationHint popup`,
+ undefined,
+ 25
+ );
+ } catch (ex) {
+ info("Got expected timeout from the waitForCondition: ", ex);
+ } finally {
+ Assert.ok(!hintDidShow, "No confirmation hint shown");
+ }
+
+ // the previous doorhanger would have old values, verify it was updated/replaced with new values from the form
+ notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "",
+ usernameValue: user1LoginSnapshot.username,
+ passwordLength: user1LoginSnapshot.password.length,
+ });
+ await cleanupDoorhanger(notif);
+
+ storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ );
+
+ // submit the form to ensure the correct updates are made
+ await submitForm(browser);
+ info("form submitted, waiting for storage changed");
+ await storageChangedPromise;
+
+ // Check the auto-saved login has not changed and only metadata on the original login updated
+ verifyLogins([
+ {
+ username: "user1",
+ password: "xyzpassword",
+ timeCreated: user1LoginSnapshot.timeCreated,
+ usedSince: user1LoginSnapshot.timeLastUsed,
+ },
+ {
+ username: "",
+ password: autoSavedLogin.password,
+ timeCreated: autoSavedLogin.timeCreated,
+ timeLastUsed: autoSavedLogin.timeLastUsed,
+ },
+ ]);
+
+ // Check we have no notifications at this point
+ Assert.ok(!PopupNotifications.isPanelOpen, "No doorhanger is open");
+ Assert.ok(
+ !PopupNotifications.getNotification("password", browser),
+ "No notifications"
+ );
+
+ // make sure the cache entry is unchanged with the removal of the auto-saved login
+ Assert.equal(
+ autoSavedLogin.password,
+ LoginManagerParent.getGeneratedPasswordsByPrincipalOrigin().get(
+ "https://example.com"
+ ).value,
+ "Generated password cache entry has the expected password value"
+ );
+ }
+ );
+});
+
+add_task(async function form_edit_username_and_password_of_generated_login() {
+ // test when changing the username and then the password in a form with a generated password (bug 1625242)
+ // * the toast is not shown for the username change as the auto-saved login is not modified
+ // * the dismissed doorhanger for the username change has the correct username and password
+ // * the toast is shown for the change to the generated password
+ // * the dismissed doorhanger for the password change has the correct username and password
+
+ await setup_withNoLogins();
+ await openFormInNewTab(
+ TEST_ORIGIN + FORM_PAGE_PATH,
+ {},
+ async function taskFn(browser) {
+ await SimpleTest.promiseFocus(browser);
+
+ // first, create an auto-saved login with generated password
+ let storageChangedPromise = TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "addLogin"
+ );
+ // We don't need to wait to confirm the hint hides itelf every time
+ let forceClosePopup = true;
+ let hintShownAndVerified = verifyConfirmationHint(
+ browser,
+ forceClosePopup
+ );
+
+ info("Filling generated password from context menu");
+ // there's no new-password field in this form so we'll use the context menu
+ await doFillGeneratedPasswordContextMenuItem(
+ browser,
+ passwordInputSelector
+ );
+
+ info("waiting for dismissed password-change notification");
+ await waitForDoorhanger(browser, "password-change");
+
+ // Make sure confirmation hint was shown
+ info("waiting for verifyConfirmationHint");
+ await hintShownAndVerified;
+
+ info("waiting for addLogin");
+ await storageChangedPromise;
+ info("addLogin promise resolved");
+ // Check properties of the newly auto-saved login
+ let [autoSavedLoginSnapshot] = verifyLogins([
+ {
+ timesUsed: 1,
+ username: "",
+ passwordLength: LoginTestUtils.generation.LENGTH,
+ },
+ ]);
+
+ let notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: "attention",
+ usernameValue: "",
+ password: autoSavedLoginSnapshot.password,
+ });
+
+ // close but don't remove the doorhanger, we want to ensure it is updated/replaced on further form edits
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ let PN = notif.owner;
+ PN.panel.hidePopup();
+ await promiseHidden;
+ await TestUtils.waitForTick();
+
+ // change the username then the password in the form
+ for (let {
+ fieldSelector,
+ fieldValue,
+ expectedConfirmation,
+ expectedDoorhangerUsername,
+ expectedDoorhangerPassword,
+ expectedDoorhangerType,
+ } of [
+ {
+ fieldSelector: usernameInputSelector,
+ fieldValue: "someuser",
+ expectedConfirmation: false,
+ expectedDoorhangerUsername: "someuser",
+ expectedDoorhangerPassword: autoSavedLoginSnapshot.password,
+ expectedDoorhangerType: "password-change",
+ },
+ {
+ fieldSelector: passwordInputSelector,
+ fieldValue: "!!",
+ expectedConfirmation: true,
+ expectedDoorhangerUsername: "someuser",
+ expectedDoorhangerPassword: autoSavedLoginSnapshot.password + "!!",
+ expectedDoorhangerType: "password-change",
+ },
+ ]) {
+ let loginModifiedPromise = expectedConfirmation
+ ? TestUtils.topicObserved(
+ "passwordmgr-storage-changed",
+ (_, data) => data == "modifyLogin"
+ )
+ : Promise.resolve();
+
+ // now edit the field value
+ let passwordEditedMessage = listenForTestNotification(
+ "PasswordEditedOrGenerated"
+ );
+ let passwordChangeDoorhangerPromise = waitForDoorhanger(
+ browser,
+ expectedDoorhangerType
+ );
+ let hintDidShow = false;
+ let hintPromiseShown = BrowserTestUtils.waitForPopupEvent(
+ document.getElementById("confirmation-hint"),
+ "shown"
+ );
+ hintPromiseShown.then(() => (hintDidShow = true));
+
+ info(`updating form: ${fieldSelector}: ${fieldValue}`);
+ await appendContentInputvalue(browser, fieldSelector, fieldValue);
+ info(
+ "form edited, waiting for test notification of PasswordEditedOrGenerated"
+ );
+ await passwordEditedMessage;
+ info(
+ "Resolved listenForTestNotification promise, waiting for doorhanger"
+ );
+ await passwordChangeDoorhangerPromise;
+ // wait for possible confirmation hint
+ try {
+ info("Waiting for hintDidShow");
+ await TestUtils.waitForCondition(
+ () => hintDidShow,
+ `Waiting for confirmationHint popup`,
+ undefined,
+ 25
+ );
+ } catch (ex) {
+ info("Got expected timeout from the waitForCondition: " + ex);
+ } finally {
+ info("confirmationHint check done, assert on hintDidShow");
+ Assert.equal(
+ hintDidShow,
+ expectedConfirmation,
+ "Confirmation hint shown"
+ );
+ }
+ info(
+ "Waiting for loginModifiedPromise, expectedConfirmation? " +
+ expectedConfirmation
+ );
+ await loginModifiedPromise;
+
+ // the previous doorhanger would have old values, verify it was updated/replaced with new values from the form
+ info("Verifying the doorhanger");
+ notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: true,
+ anchorExtraAttr: expectedConfirmation ? "attention" : "",
+ usernameValue: expectedDoorhangerUsername,
+ passwordLength: expectedDoorhangerPassword.length,
+ });
+ await cleanupDoorhanger(notif);
+ }
+
+ // submit the form to verify we still get the right doorhanger values
+ let passwordChangeDoorhangerPromise = waitForDoorhanger(
+ browser,
+ "password-change"
+ );
+ await submitForm(browser);
+ info("form submitted, waiting for doorhanger");
+ await passwordChangeDoorhangerPromise;
+ notif = await openAndVerifyDoorhanger(browser, "password-change", {
+ dismissed: false,
+ anchorExtraAttr: "",
+ usernameValue: "someuser",
+ passwordLength: LoginTestUtils.generation.LENGTH + 2,
+ });
+ await cleanupDoorhanger(notif);
+ }
+ );
+});