/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { html } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/backup/password-rules-tooltip.mjs"; /** * The widget for enabling password protection if the backup is not yet * encrypted. */ export default class PasswordValidationInputs extends MozLitElement { static properties = { _hasEmail: { type: Boolean, state: true }, _passwordsMatch: { type: Boolean, state: true }, _passwordsValid: { type: Boolean, state: true }, _tooShort: { type: Boolean, state: true }, createPasswordLabelL10nId: { type: String, reflect: true, attribute: "create-password-label-l10n-id", }, embeddedFxBackupOptIn: { type: Boolean, reflect: true, attribute: "embedded-fx-backup-opt-in", }, }; static get queries() { return { formEl: "#password-inputs-form", inputNewPasswordEl: "#new-password-input", inputRepeatPasswordEl: "#repeat-password-input", passwordRulesEl: "#password-rules", repeatPasswordErrorEl: "#repeat-password-error", }; } constructor() { super(); this._tooShort = true; this._hasEmail = false; this._passwordsMatch = false; this._passwordsValid = false; } connectedCallback() { super.connectedCallback(); this._onKeydown = e => { if (e.key === "Escape" && this.passwordRulesEl.open) { this.passwordRulesEl.hide(); e.stopPropagation(); e.preventDefault(); } }; document.addEventListener("keydown", this._onKeydown, true); } disconnectedCallback() { document.removeEventListener("keydown", this._onKeydown, true); super.disconnectedCallback(); } setInputValidity(input, isValid, describedById = null) { input.setAttribute("aria-invalid", isValid ? "false" : "true"); if (describedById) { input.setAttribute("aria-describedby", describedById); } else { input.removeAttribute("aria-describedby"); } } reset() { this.formEl?.reset(); if (this.inputNewPasswordEl) { this.inputNewPasswordEl.revealPassword = false; this.setInputValidity(this.inputNewPasswordEl, true); } if (this.inputRepeatPasswordEl) { this.inputRepeatPasswordEl.revealPassword = false; this.setInputValidity(this.inputRepeatPasswordEl, true); } this._hasEmail = false; this._tooShort = true; this._passwordsMatch = false; this._passwordsValid = false; this.passwordRulesEl.hide(); } handleFocusNewPassword() { this.passwordRulesEl.show(); } handleBlurNewPassword(event) { if (event.target.checkValidity()) { this.passwordRulesEl.hide(); } } handleChangeNewPassword() { this.updatePasswordValidity(); } handleChangeRepeatPassword() { this.updatePasswordValidity(); } updatePasswordValidity() { const emailRegex = /^[\w!#$%&'*+/=?^`{|}~.-]+@[A-Z0-9-]+\.[A-Z0-9.-]+$/i; const l10n = new Localization(["browser/backupSettings.ftl"], true); this._hasEmail = emailRegex.test(this.inputNewPasswordEl.value); if (this._hasEmail) { const invalid_password_email_l10n_message = l10n.formatValueSync( "password-validity-has-email" ); this.inputNewPasswordEl.setCustomValidity( invalid_password_email_l10n_message ); } else { this.inputNewPasswordEl.setCustomValidity(""); } const newPassValidity = this.inputNewPasswordEl.validity; this._tooShort = newPassValidity?.valueMissing || newPassValidity?.tooShort; const newInvalid = !newPassValidity?.valid; this.setInputValidity( this.inputNewPasswordEl, !newInvalid, "password-rules-tooltip" ); this._passwordsMatch = this.inputNewPasswordEl.value == this.inputRepeatPasswordEl.value; if (!this._passwordsMatch) { this.inputRepeatPasswordEl.setCustomValidity( l10n.formatValueSync("password-validity-do-not-match") ); this.setInputValidity( this.inputRepeatPasswordEl, false, "repeat-password-error" ); document.l10n.setAttributes( this.repeatPasswordErrorEl, "password-validity-do-not-match" ); } else { this.inputRepeatPasswordEl.setCustomValidity(""); this.setInputValidity(this.inputRepeatPasswordEl, true); } const repeatPassValidity = this.inputRepeatPasswordEl.validity; this._passwordsValid = newPassValidity?.valid && repeatPassValidity?.valid && this._passwordsMatch; } /** * Dispatches a custom event whenever validity changes. * * @param {Map} changedProperties a Map of recently changed properties and their new values */ updated(changedProperties) { if (!changedProperties.has("_passwordsValid")) { return; } if (this._passwordsValid) { this.dispatchEvent( new CustomEvent("ValidPasswordsDetected", { bubbles: true, composed: true, detail: { password: this.inputNewPasswordEl.value, }, }) ); } else { this.dispatchEvent( new CustomEvent("InvalidPasswordsDetected", { bubbles: true, composed: true, }) ); } } contentTemplate() { return html`
`; } render() { return html` ${this.contentTemplate()} `; } } customElements.define("password-validation-inputs", PasswordValidationInputs);