summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/browser/browser_doorhanger_form_password_edit.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/browser/browser_doorhanger_form_password_edit.js')
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_form_password_edit.js562
1 files changed, 562 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_form_password_edit.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_form_password_edit.js
new file mode 100644
index 0000000000..23afd2c6ab
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_form_password_edit.js
@@ -0,0 +1,562 @@
+/**
+ * Test changed (not submitted) passwords produce the right doorhangers/notifications
+ */
+
+/* eslint no-shadow:"off" */
+
+"use strict";
+
+// The origin for the test URIs.
+const TEST_ORIGIN = "https://example.com";
+const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
+const passwordInputSelector = "#form-basic-password";
+const usernameInputSelector = "#form-basic-username";
+
+let testCases = [
+ {
+ name: "Enter password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "abcXYZ",
+ },
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ doorhanger: {
+ type: "password-save",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "",
+ password: "abcXYZ",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Change password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [],
+ formDefaults: {
+ [passwordInputSelector]: "pass1",
+ },
+ formChanges: {
+ [passwordInputSelector]: "pass-changed",
+ },
+ expected: {
+ initialForm: {
+ username: "",
+ password: "pass1",
+ },
+ doorhanger: {
+ type: "password-save",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "",
+ password: "pass-changed",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Change autofilled password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "user1", password: "autopass" }],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "autopass-changed",
+ },
+ expected: {
+ initialForm: {
+ username: "user1",
+ password: "autopass",
+ },
+ doorhanger: {
+ type: "password-change",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "user1",
+ password: "autopass-changed",
+ },
+ },
+ },
+ {
+ name: "Change autofilled username and password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "user1", password: "pass1" }],
+ formDefaults: {},
+ formChanges: {
+ [usernameInputSelector]: "user2",
+ [passwordInputSelector]: "pass2",
+ },
+ expected: {
+ initialForm: {
+ username: "user1",
+ password: "pass1",
+ },
+ doorhanger: {
+ type: "password-save",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "user2",
+ password: "pass2",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Change password pref disabled",
+ prefEnabled: false,
+ isLoggedIn: true,
+ logins: [],
+ formDefaults: {
+ [passwordInputSelector]: "pass1",
+ },
+ formChanges: {
+ [passwordInputSelector]: "pass-changed",
+ },
+ expected: {
+ initialForm: {
+ username: "",
+ password: "pass1",
+ },
+ doorhanger: null,
+ },
+ },
+ {
+ name: "Change to new username",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "user1", password: "pass1" }],
+ formDefaults: {},
+ formChanges: {
+ [usernameInputSelector]: "user2",
+ },
+ expected: {
+ initialForm: {
+ username: "user1",
+ password: "pass1",
+ },
+ doorhanger: {
+ type: "password-save",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "user2",
+ password: "pass1",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Change to existing username, different password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "user-saved", password: "pass1" }],
+ formDefaults: {
+ [usernameInputSelector]: "user-prefilled",
+ [passwordInputSelector]: "pass2",
+ },
+ formChanges: {
+ [usernameInputSelector]: "user-saved",
+ },
+ expected: {
+ initialForm: {
+ username: "user-prefilled",
+ password: "pass2",
+ },
+ doorhanger: {
+ type: "password-change",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "user-saved",
+ password: "pass2",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Add username to existing password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "", password: "pass1" }],
+ formDefaults: {},
+ formChanges: {
+ [usernameInputSelector]: "user1",
+ },
+ expected: {
+ initialForm: {
+ username: "",
+ password: "pass1",
+ },
+ doorhanger: {
+ type: "password-change",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "user1",
+ password: "pass1",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Change to existing username, password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "user1", password: "pass1" }],
+ formDefaults: {
+ [usernameInputSelector]: "user",
+ [passwordInputSelector]: "pass",
+ },
+ formChanges: {
+ [passwordInputSelector]: "pass1",
+ [usernameInputSelector]: "user1",
+ },
+ expected: {
+ initialForm: {
+ username: "user",
+ password: "pass",
+ },
+ doorhanger: null,
+ },
+ },
+ {
+ name: "Ensure a dismissed password-save doorhanger appears on an input event when editing an unsaved password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "a",
+ },
+ shouldBlur: false,
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ doorhanger: {
+ type: "password-save",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "",
+ password: "a",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Ensure a dismissed password-save doorhanger appears with the latest input value upon editing an unsaved password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "a",
+ [passwordInputSelector]: "ab",
+ [passwordInputSelector]: "abc",
+ },
+ shouldBlur: false,
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ doorhanger: {
+ type: "password-save",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "",
+ password: "abc",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Ensure a dismissed password-change doorhanger appears on an input event when editing a saved password",
+ prefEnabled: true,
+ isLoggedIn: true,
+ logins: [{ username: "", password: "pass1" }],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "pass",
+ },
+ shouldBlur: false,
+ expected: {
+ initialForm: {
+ username: "",
+ password: "pass1",
+ },
+ doorhanger: {
+ type: "password-change",
+ dismissed: true,
+ anchorExtraAttr: "",
+ username: "",
+ password: "pass",
+ toggle: "visible",
+ },
+ },
+ },
+ {
+ name: "Ensure no dismissed doorhanger is shown on 'input' when Primary Password is locked",
+ prefEnabled: true,
+ isLoggedIn: false,
+ logins: [],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "pass",
+ },
+ shouldBlur: false,
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ doorhanger: null,
+ },
+ },
+ {
+ name: "Ensure no dismissed doorhanger is shown on 'change' when Primary Password is locked",
+ prefEnabled: true,
+ isLoggedIn: false,
+ logins: [],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "pass",
+ },
+ shouldBlur: true,
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ doorhanger: null,
+ },
+ },
+];
+
+requestLongerTimeout(2);
+SimpleTest.requestCompleteLog();
+
+for (let testData of testCases) {
+ let tmp = {
+ async [testData.name]() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.passwordEditCapture.enabled", testData.prefEnabled]],
+ });
+ if (!testData.isLoggedIn) {
+ // Enable Primary Password
+ LoginTestUtils.primaryPassword.enable();
+ }
+ for (let passwordFieldType of ["password", "text"]) {
+ info(
+ "testing with type=" +
+ passwordFieldType +
+ ": " +
+ JSON.stringify(testData)
+ );
+ await testPasswordChange(testData, { passwordFieldType });
+ }
+ if (!testData.isLoggedIn) {
+ LoginTestUtils.primaryPassword.disable();
+ }
+ await SpecialPowers.popPrefEnv();
+ },
+ };
+ add_task(tmp[testData.name]);
+}
+
+async function testPasswordChange(
+ {
+ logins = [],
+ formDefaults = {},
+ formChanges = {},
+ expected,
+ isLoggedIn,
+ shouldBlur = true,
+ },
+ { passwordFieldType }
+) {
+ await LoginTestUtils.clearData();
+ await cleanupDoorhanger();
+
+ let url = TEST_ORIGIN + BASIC_FORM_PAGE_PATH;
+ for (let login of logins) {
+ await LoginTestUtils.addLogin(login);
+ }
+
+ for (let login of await Services.logins.getAllLogins()) {
+ info(`Saved login: ${login.username}, ${login.password}, ${login.origin}`);
+ }
+
+ let formProcessedPromise = listenForTestNotification("FormProcessed");
+ info("Opening tab with url: " + url);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function (browser) {
+ info(`Opened tab with url: ${url}, waiting for focus`);
+ await SimpleTest.promiseFocus(browser.ownerGlobal);
+ info("Waiting for form-processed message");
+ await formProcessedPromise;
+ await initForm(browser, formDefaults, { passwordFieldType });
+ await checkForm(browser, expected.initialForm);
+ info("form checked");
+
+ // A message is still sent to the parent process when Primary Password is enabled
+ let notificationMessage =
+ expected.doorhanger || !isLoggedIn
+ ? "PasswordEditedOrGenerated"
+ : "PasswordIgnoreEdit";
+ let passwordTestNotification =
+ listenForTestNotification(notificationMessage);
+
+ await changeContentFormValues(browser, formChanges, shouldBlur);
+
+ info(
+ `form edited, waiting for test notification of ${notificationMessage}`
+ );
+
+ await passwordTestNotification;
+ info("Resolved passwordTestNotification promise");
+
+ if (!expected.doorhanger) {
+ let notif;
+ try {
+ await TestUtils.waitForCondition(
+ () => {
+ return (notif = PopupNotifications.getNotification(
+ "password",
+ browser
+ ));
+ },
+ `Waiting to ensure no notification`,
+ undefined,
+ 25
+ );
+ } catch (ex) {}
+ Assert.ok(!notif, "No doorhanger expected");
+ // the remainder of the test is for doorhanger-expected cases
+ return;
+ }
+
+ let notificationType = expected.doorhanger.type;
+ Assert.ok(
+ /^password-save|password-change$/.test(notificationType),
+ "test provided an expected notification type: " + notificationType
+ );
+ info("waiting for doorhanger");
+ await waitForDoorhanger(browser, notificationType);
+
+ info("verifying doorhanger");
+ let notif = await openAndVerifyDoorhanger(
+ browser,
+ notificationType,
+ expected.doorhanger
+ );
+ Assert.ok(notif, "Doorhanger was shown");
+
+ let promiseHidden = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
+ await promiseHidden;
+
+ info("cleanup doorhanger");
+ await cleanupDoorhanger(notif);
+ }
+ );
+}
+
+async function initForm(browser, formDefaults, passwordFieldType) {
+ await ContentTask.spawn(
+ browser,
+ { passwordInputSelector, passwordFieldType },
+ async function ({ passwordInputSelector, passwordFieldType }) {
+ content.document.querySelector(passwordInputSelector).type =
+ passwordFieldType;
+ }
+ );
+ await ContentTask.spawn(
+ browser,
+ formDefaults,
+ async function (selectorValues) {
+ for (let [sel, value] of Object.entries(selectorValues)) {
+ content.document.querySelector(sel).value = value;
+ }
+ }
+ );
+}
+
+async function checkForm(browser, expected) {
+ await ContentTask.spawn(
+ browser,
+ {
+ [passwordInputSelector]: expected.password,
+ [usernameInputSelector]: expected.username,
+ },
+ async function contentCheckForm(selectorValues) {
+ for (let [sel, value] of Object.entries(selectorValues)) {
+ let field = content.document.querySelector(sel);
+ Assert.equal(
+ field.value,
+ value,
+ sel + " has the expected initial value"
+ );
+ }
+ }
+ );
+}
+
+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
+ Assert.equal(panel.state, "closed", "Panel is initially closed");
+ let promiseShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ // synthesize click on anchor as this also blurs the form field triggering
+ // a change event
+ EventUtils.synthesizeMouseAtCenter(notif.anchorElement, {});
+ await promiseShown;
+ await Promise.resolve();
+ await checkDoorhangerUsernamePassword(expected.username, expected.password);
+
+ let notificationElement = PopupNotifications.panel.childNodes[0];
+ let checkbox = notificationElement.querySelector(
+ "#password-notification-visibilityToggle"
+ );
+
+ if (expected.toggle == "visible") {
+ // Bug 1692284
+ // Assert.ok(BrowserTestUtils.isVisible(checkbox), "Toggle checkbox visible as expected");
+ } else if (expected.toggle == "hidden") {
+ Assert.ok(
+ BrowserTestUtils.isHidden(checkbox),
+ "Toggle checkbox hidden as expected"
+ );
+ } else {
+ info("Not checking toggle checkbox visibility");
+ }
+ return notif;
+}