tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit cff6812467590c42d7ffd62ff42c8cbe49624af7
parent bb394607c19d28513b93074216de5ae93fb7af6f
Author: mailelucks <maile.lucks@gmail.com>
Date:   Sat, 22 Nov 2025 13:47:04 +0000

Bug 1996184 - Remove single `this.isInvalid` that was being used to track both origin and password validity r=mtigley,credential-management-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D270884

Diffstat:
Mbrowser/components/aboutlogins/content/components/input-field/login-password-field.mjs | 6+++++-
Mtoolkit/components/satchel/megalist/content/components/login-form/login-form.mjs | 31+++++++++++++++++++++++++++----
Mtoolkit/components/satchel/megalist/content/tests/browser/browser_passwords_create_login.js | 115++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 141 insertions(+), 11 deletions(-)

diff --git a/browser/components/aboutlogins/content/components/input-field/login-password-field.mjs b/browser/components/aboutlogins/content/components/input-field/login-password-field.mjs @@ -41,7 +41,11 @@ class LoginPasswordField extends MozLitElement { } get #password() { - return !this.newPassword && this.concealed && this.value + if (!this.value) { + return ""; + } + + return !this.newPassword && this.concealed ? LoginPasswordField.CONCEALED_PASSWORD_TEXT : this.value; } diff --git a/toolkit/components/satchel/megalist/content/components/login-form/login-form.mjs b/toolkit/components/satchel/megalist/content/components/login-form/login-form.mjs @@ -27,6 +27,8 @@ export class LoginForm extends MozLitElement { passwordValue: { type: String }, passwordVisible: { type: Boolean }, _showDeleteCard: { type: Boolean, state: true }, + _originInvalid: { type: Boolean, state: true }, + _passwordInvalid: { type: Boolean, state: true }, }; static queries = { @@ -43,8 +45,9 @@ export class LoginForm extends MozLitElement { this.originValue = ""; this.usernameValue = ""; this.passwordValue = ""; - this.isInvalid = false; this._showDeleteCard = false; + this._originInvalid = false; + this._passwordInvalid = false; } async firstUpdated() { @@ -97,7 +100,12 @@ export class LoginForm extends MozLitElement { } #shouldShowWarning(field, input, warning) { - if (!input.checkValidity() || this.isInvalid) { + const fieldInvalid = + warning === this.originWarning + ? this._originInvalid + : this._passwordInvalid; + + if (!input.checkValidity() || fieldInvalid) { // FIXME: for some reason checkValidity does not apply the :invalid style // to the field. For now, we reset the input value to "" apply :invalid // styling. @@ -107,11 +115,21 @@ export class LoginForm extends MozLitElement { warning.setAttribute("message", input.validationMessage); warning.classList.add("invalid-input"); field.setAttribute("aria-describedby", warning.id); + if (warning === this.originWarning) { + this._originInvalid = true; + } else if (warning === this.passwordWarning) { + this._passwordInvalid = true; + } return true; } field.removeAttribute("aria-describedby"); this.#removeWarning(warning); + if (warning === this.originWarning) { + this._originInvalid = false; + } else if (warning === this.passwordWarning) { + this._passwordInvalid = false; + } return false; } @@ -119,13 +137,18 @@ export class LoginForm extends MozLitElement { const field = e.target; const warning = field.name === "origin" ? this.originWarning : this.passwordWarning; + const isValid = field.input.checkValidity(); - if (field.input.checkValidity()) { + if (isValid) { this.#removeWarning(warning); field.removeAttribute("aria-describedby"); } - this.isInvalid = !field.input.checkValidity(); + if (field.name === "origin") { + this._originInvalid = !isValid; + } else if (field.name === "password") { + this._passwordInvalid = !isValid; + } } onCancel(e) { diff --git a/toolkit/components/satchel/megalist/content/tests/browser/browser_passwords_create_login.js b/toolkit/components/satchel/megalist/content/tests/browser/browser_passwords_create_login.js @@ -60,7 +60,10 @@ function waitForPopup(megalist, element) { info(`Wait for ${element} popup`); const loginForm = megalist.querySelector("login-form"); const popupPromise = BrowserTestUtils.waitForCondition( - () => loginForm.shadowRoot.querySelector(`${element}`), + () => + loginForm.shadowRoot + .querySelector(`${element}`) + .classList.contains("invalid-input"), `${element} popup did not render.` ); return popupPromise; @@ -166,24 +169,124 @@ add_task(async function test_add_login_empty_origin() { SidebarController.hide(); }); -add_task(async function test_add_login_empty_password() { +add_task(async function test_add_login_empty_password_and_resubmit() { const megalist = await openPasswordsSidebar(); await waitForSnapshots(); await openLoginForm(megalist, false); - addLogin(megalist, { - ...TEST_LOGIN_1, - password: "", - }); + const loginForm = megalist.querySelector("login-form"); + setInputValue(loginForm, "login-origin-field", TEST_LOGIN_1.origin); + setInputValue(loginForm, "login-username-field", TEST_LOGIN_1.username); + setInputValue(loginForm, "login-password-field", ""); + + const saveButton = loginForm.shadowRoot.querySelector( + "moz-button[type=primary]" + ); + info("Submitting empty password once."); + saveButton.buttonEl.click(); + + await waitForPopup(megalist, "password-warning"); + ok( + !loginForm.shadowRoot + .querySelector("origin-warning") + .classList.contains("invalid-input"), + "Origin field should not be marked invalid when only the password is empty." + ); + info("Submitting empty password a second time without changing value."); + saveButton.buttonEl.click(); await waitForPopup(megalist, "password-warning"); + ok( + loginForm.isConnected, + "Login form remains open after repeated invalid submissions." + ); + const logins = await Services.logins.getAllLogins(); is(logins.length, 0, "No login was added after submitting form."); + info("Entering a valid password clears the warning."); + setInputValue(loginForm, "login-password-field", TEST_LOGIN_1.password); + await BrowserTestUtils.waitForCondition( + () => + !loginForm.shadowRoot + .querySelector("password-warning") + .classList.contains("invalid-input"), + "Password warning should be removed after entering a valid password." + ); + LoginTestUtils.clearData(); info("Closing the sidebar"); SidebarController.hide(); }); +add_task(async function test_edit_login_empty_password_requires_new_value() { + const login = TEST_LOGIN_1; + await LoginTestUtils.addLogin(login); + + const megalist = await openPasswordsSidebar(); + await checkAllLoginsRendered(megalist); + + const passwordCard = megalist.querySelector("password-card"); + await waitForReauth(() => passwordCard.editBtn.click()); + await BrowserTestUtils.waitForCondition( + () => megalist.querySelector("login-form"), + "Login form failed to render in edit mode." + ); + + let loginForm = megalist.querySelector("login-form"); + setInputValue(loginForm, "login-password-field", ""); + + const saveButton = loginForm.shadowRoot.querySelector( + "moz-button[type=primary]" + ); + info("Submitting edit form with empty password."); + saveButton.buttonEl.click(); + await waitForPopup(megalist, "password-warning"); + + const passwordField = loginForm.shadowRoot.querySelector( + "login-password-field" + ); + await BrowserTestUtils.waitForCondition( + () => passwordField.input.value === "", + "Password input should remain empty after an invalid submission." + ); + is(passwordField.value, "", "Password component state cleared."); + + info("Trying to save again after the first error."); + saveButton.buttonEl.click(); + await BrowserTestUtils.waitForCondition(() => { + const form = megalist.querySelector("login-form"); + return ( + form && + form.shadowRoot + .querySelector("password-warning") + .classList.contains("invalid-input") + ); + }, "Password warning should persist after repeated invalid edits."); + + loginForm = megalist.querySelector("login-form"); + ok(loginForm, "Login form remains open after invalid edit submissions."); + + const logins = await Services.logins.getAllLogins(); + Assert.equal(logins.length, 1, "Stored login count unchanged."); + Assert.equal( + logins[0].password, + login.password, + "Existing login password not overwritten with empty value." + ); + + LoginTestUtils.clearData(); + info("Closing the sidebar"); + SidebarController.hide(); + const closeWithoutSavingButton = await BrowserTestUtils.waitForCondition(() => + megalist + .querySelector("notification-message-bar") + ?.shadowRoot.querySelector("moz-message-bar") + ?.querySelector("#primary-action") + ); + info("Closing without saving"); + closeWithoutSavingButton.buttonEl.click(); +}); + add_task(async function test_view_login_command() { Services.fog.testResetFOG(); await Services.fog.testFlushAllChildren();