summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/browser/browser_doorhanger_toggles.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/browser/browser_doorhanger_toggles.js')
-rw-r--r--toolkit/components/passwordmgr/test/browser/browser_doorhanger_toggles.js478
1 files changed, 478 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/browser/browser_doorhanger_toggles.js b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_toggles.js
new file mode 100644
index 0000000000..f529369522
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_doorhanger_toggles.js
@@ -0,0 +1,478 @@
+/* eslint no-shadow:"off" */
+
+const passwordInputSelector = "#form-basic-password";
+const usernameInputSelector = "#form-basic-username";
+const FORM_URL =
+ "https://example.com/browser/toolkit/components/passwordmgr/test/browser/form_basic.html";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.rememberSignons.visibilityToggle", true]],
+ });
+});
+
+let testCases = [
+ {
+ /* Test that the doorhanger password field shows plain or * text
+ * when the checkbox is checked.
+ */
+ name: "test_toggle_password",
+ logins: [],
+ enabledPrimaryPassword: false,
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "pw",
+ [usernameInputSelector]: "username",
+ },
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ passwordChangedDoorhanger: null,
+ submitDoorhanger: {
+ type: "password-save",
+ dismissed: false,
+ username: "username",
+ password: "pw",
+ toggleVisible: true,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ afterToggleClick0: {
+ inputType: "text",
+ toggleChecked: true,
+ },
+ afterToggleClick1: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ },
+ },
+ {
+ /* Test that the doorhanger password toggle checkbox is disabled
+ * when the primary password is set.
+ */
+ name: "test_checkbox_disabled_if_has_primary_password",
+ logins: [],
+ enabledPrimaryPassword: true,
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "pass",
+ [usernameInputSelector]: "username",
+ },
+ expected: {
+ initialForm: {
+ username: "",
+ password: "",
+ },
+ passwordChangedDoorhanger: null,
+ submitDoorhanger: {
+ type: "password-save",
+ dismissed: false,
+ username: "username",
+ password: "pass",
+ toggleVisible: false,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ },
+ },
+ {
+ /* Test that the reveal password checkbox is hidden when editing the
+ * password of an autofilled login
+ */
+ name: "test_edit_autofilled_password",
+ logins: [{ username: "username1", password: "password" }],
+ formDefaults: {},
+ formChanges: {
+ [passwordInputSelector]: "password!",
+ },
+ expected: {
+ initialForm: {
+ username: "username1",
+ password: "password",
+ },
+ passwordChangedDoorhanger: {
+ type: "password-change",
+ dismissed: true,
+ username: "username1",
+ password: "password!",
+ toggleVisible: false,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ submitDoorhanger: {
+ type: "password-change",
+ dismissed: false,
+ username: "username1",
+ password: "password!",
+ toggleVisible: false,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ },
+ },
+ {
+ /* Test that the reveal password checkbox is shown when editing the
+ * password of a login that has been autofilled and then deleted
+ */
+ name: "test_autofilled_cleared_then_updated_password",
+ logins: [{ username: "username1", password: "password" }],
+ formDefaults: {},
+ formChanges: [
+ {
+ [passwordInputSelector]: "",
+ },
+ {
+ [passwordInputSelector]: "password!",
+ },
+ ],
+ expected: {
+ initialForm: {
+ username: "username1",
+ password: "password",
+ },
+ passwordChangedDoorhanger: {
+ type: "password-change",
+ dismissed: true,
+ username: "username1",
+ password: "password!",
+ toggleVisible: true,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ submitDoorhanger: {
+ type: "password-change",
+ dismissed: false,
+ username: "username1",
+ password: "password!",
+ toggleVisible: true,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ },
+ },
+ {
+ /* Test that the reveal password checkbox is hidden when editing the
+ * username of an autofilled login
+ */
+ name: "test_edit_autofilled_username",
+ logins: [{ username: "username1", password: "password" }],
+ formDefaults: {},
+ formChanges: {
+ [usernameInputSelector]: "username2",
+ },
+ expected: {
+ initialForm: {
+ username: "username1",
+ password: "password",
+ },
+ passwordChangedDoorhanger: {
+ type: "password-save",
+ dismissed: true,
+ username: "username2",
+ password: "password",
+ toggleVisible: false,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ submitDoorhanger: {
+ type: "password-save",
+ dismissed: false,
+ username: "username2",
+ password: "password",
+ toggleVisible: false,
+ initialToggleState: {
+ inputType: "password",
+ toggleChecked: false,
+ },
+ },
+ },
+ },
+];
+
+for (let testData of testCases) {
+ if (testData.skip) {
+ info("Skipping test:", testData.name);
+ continue;
+ }
+ let tmp = {
+ async [testData.name]() {
+ await testDoorhangerToggles(testData);
+ },
+ };
+ add_task(tmp[testData.name]);
+}
+
+/**
+ * Set initial test conditions,
+ * Load and populate the form,
+ * Submit it and verify doorhanger toggle behavior
+ */
+async function testDoorhangerToggles({
+ logins = [],
+ formDefaults = {},
+ formChanges = {},
+ expected,
+ enabledPrimaryPassword,
+}) {
+ formChanges = Array.isArray(formChanges) ? formChanges : [formChanges];
+
+ for (let login of logins) {
+ await LoginTestUtils.addLogin(login);
+ }
+ if (enabledPrimaryPassword) {
+ LoginTestUtils.primaryPassword.enable();
+ }
+ let formProcessedPromise = listenForTestNotification("FormProcessed");
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: FORM_URL,
+ },
+ async function (browser) {
+ info(`Opened tab with url: ${FORM_URL}, waiting for focus`);
+ await SimpleTest.promiseFocus(browser.ownerGlobal);
+ info("Waiting for form-processed message");
+ await formProcessedPromise;
+ await initForm(browser, formDefaults);
+ await checkForm(browser, expected.initialForm);
+ info("form checked");
+
+ // some tests check the dismissed doorhanger from editing the password
+ let formChanged = expected.passwordChangedDoorhanger
+ ? listenForTestNotification("PasswordEditedOrGenerated")
+ : Promise.resolve();
+ for (let change of formChanges) {
+ await changeContentFormValues(browser, change, {
+ method: "paste_text",
+ });
+ }
+
+ await formChanged;
+
+ if (expected.passwordChangedDoorhanger) {
+ let expectedDoorhanger = expected.passwordChangedDoorhanger;
+ info("Verifying dismissed doorhanger from password change");
+ let notif = await waitForDoorhanger(browser, expectedDoorhanger.type);
+ Assert.ok(notif, "got notification popup");
+ Assert.equal(
+ notif.dismissed,
+ expectedDoorhanger.dismissed,
+ "Check notification dismissed property"
+ );
+ let { panel } = browser.ownerGlobal.PopupNotifications;
+ // we will open dismissed doorhanger to check panel contents
+ Assert.equal(panel.state, "closed", "Panel is initially closed");
+ let promiseShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ info("Opening the doorhanger popup");
+ // synthesize click on anchor as this also blurs the form field triggering
+ // a change event
+ EventUtils.synthesizeMouseAtCenter(notif.anchorElement, {});
+ await promiseShown;
+ await TestUtils.waitForTick();
+ Assert.ok(
+ panel.children.length,
+ `Check the popup has at least one notification (${panel.children.length})`
+ );
+
+ // Check the password-changed-capture doorhanger contents & behaviour
+ info("Verifying the doorhanger");
+ await verifyDoorhangerToggles(browser, notif, expectedDoorhanger);
+ await hideDoorhangerPopup(notif);
+ }
+
+ if (expected.submitDoorhanger) {
+ let expectedDoorhanger = expected.submitDoorhanger;
+ let { panel } = browser.ownerGlobal.PopupNotifications;
+ // submit the form and wait for the doorhanger
+ info("Submitting the form");
+ let submittedPromise = listenForTestNotification("ShowDoorhanger");
+ let promiseShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+ await submitForm(browser, "/");
+ await submittedPromise;
+ info("Waiting for doorhanger popup to open");
+ await promiseShown;
+ let notif = await getCaptureDoorhanger(expectedDoorhanger.type);
+ Assert.ok(notif, "got notification popup");
+ Assert.equal(
+ notif.dismissed,
+ expectedDoorhanger.dismissed,
+ "Check notification dismissed property"
+ );
+ Assert.ok(
+ panel.children.length,
+ `Check the popup has at least one notification (${panel.children.length})`
+ );
+
+ // Check the submit-capture doorhanger contents & behaviour
+ info("Verifying the submit doorhanger");
+ await verifyDoorhangerToggles(browser, notif, expectedDoorhanger);
+ await cleanupDoorhanger(notif);
+ }
+ }
+ );
+ await LoginTestUtils.clearData();
+ if (enabledPrimaryPassword) {
+ LoginTestUtils.primaryPassword.disable();
+ }
+ await cleanupPasswordNotifications();
+}
+
+// --------------------------------------------------------------------
+// Helpers
+
+async function verifyDoorhangerToggles(browser, notif, expected) {
+ let { initialToggleState, afterToggleClick0, afterToggleClick1 } = expected;
+
+ let { panel } = browser.ownerGlobal.PopupNotifications;
+ let notificationElement = panel.childNodes[0];
+ let passwordTextbox = notificationElement.querySelector(
+ "#password-notification-password"
+ );
+ let toggleCheckbox = notificationElement.querySelector(
+ "#password-notification-visibilityToggle"
+ );
+ Assert.equal(panel.state, "open", "Panel is open");
+ Assert.ok(
+ BrowserTestUtils.is_visible(passwordTextbox),
+ "The doorhanger password field is visible"
+ );
+
+ await checkDoorhangerUsernamePassword(expected.username, expected.password);
+ if (expected.toggleVisible) {
+ Assert.ok(
+ BrowserTestUtils.is_visible(toggleCheckbox),
+ "The visibility checkbox is shown"
+ );
+ } else {
+ Assert.ok(
+ BrowserTestUtils.is_hidden(toggleCheckbox),
+ "The visibility checkbox is hidden"
+ );
+ }
+
+ if (initialToggleState) {
+ Assert.equal(
+ toggleCheckbox.checked,
+ initialToggleState.toggleChecked,
+ `Initially, toggle is ${
+ initialToggleState.toggleChecked ? "checked" : "unchecked"
+ }`
+ );
+ Assert.equal(
+ passwordTextbox.type,
+ initialToggleState.inputType,
+ `Initially, password input has type: ${initialToggleState.inputType}`
+ );
+ }
+ if (afterToggleClick0) {
+ Assert.ok(
+ !toggleCheckbox.hidden,
+ "The checkbox shouldnt be hidden when clicking on it"
+ );
+ info("Clicking on the visibility toggle");
+ await EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {});
+ await TestUtils.waitForTick();
+ Assert.equal(
+ toggleCheckbox.checked,
+ afterToggleClick0.toggleChecked,
+ `After 1st click, expect toggle to be checked? ${afterToggleClick0.toggleChecked}, actual: ${toggleCheckbox.checked}`
+ );
+ Assert.equal(
+ passwordTextbox.type,
+ afterToggleClick0.inputType,
+ `After 1st click, expect password input to have type: ${afterToggleClick0.inputType}`
+ );
+ }
+ if (afterToggleClick1) {
+ Assert.ok(
+ !toggleCheckbox.hidden,
+ "The checkbox shouldnt be hidden when clicking on it"
+ );
+ info("Clicking on the visibility toggle again");
+ await EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {});
+ await TestUtils.waitForTick();
+ Assert.equal(
+ toggleCheckbox.checked,
+ afterToggleClick1.toggleChecked,
+ `After 2nd click, expect toggle to be checked? ${afterToggleClick0.toggleChecked}, actual: ${toggleCheckbox.checked}`
+ );
+ Assert.equal(
+ passwordTextbox.type,
+ afterToggleClick1.inputType,
+ `After 2nd click, expect password input to have type: ${afterToggleClick1.inputType}`
+ );
+ }
+}
+
+async function initForm(browser, formDefaults) {
+ 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 submitForm(browser, action = "") {
+ // Submit the form
+ let correctPathNamePromise = BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(browser, [action], async function (actionPathname) {
+ let form = content.document.querySelector("form");
+ if (actionPathname) {
+ form.action = actionPathname;
+ }
+ info("Submitting form to:" + form.action);
+ form.submit();
+ info("Submitted the form");
+ });
+ await correctPathNamePromise;
+ await SpecialPowers.spawn(browser, [action], async actionPathname => {
+ let win = content;
+ await ContentTaskUtils.waitForCondition(() => {
+ return (
+ win.location.pathname == actionPathname &&
+ win.document.readyState == "complete"
+ );
+ }, "Wait for form submission load");
+ });
+}