gecko-dev/browser/components/backup/content/password-validation-inputs.mjs
Stephen Thompson 6c1381ca4b Bug 1901308 - Show profile backup errors and keep modals open r=backup-reviewers,fluent-reviewers,firefox-desktop-core-reviewers ,mconley,bolsson
Currently, clicking "confirm" buttons on modals in the profile backup settings menu will always close the modals regardless of whether the operation succeeded or failed. In the case of errors, users don't know that something went wrong. It's better to keep the modals open and display an error so that the user knows what to do next and can try to fix the issue, if applicable.

Differential Revision: https://phabricator.services.mozilla.com/D218358
2024-08-06 18:21:16 +00:00

214 lines
6.6 KiB
JavaScript

/* 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 = {
_hasCommon: { type: Boolean, state: true },
_hasEmail: { type: Boolean, state: true },
_passwordsMatch: { type: Boolean, state: true },
_passwordsValid: { type: Boolean, state: true },
_showRules: { type: Boolean, state: true },
_tooShort: { type: Boolean, state: true },
/**
* If, by chance, there is focus on a focusable element in the tooltip,
* track the focus state so that we can keep the tooltip open.
*/
_tooltipFocus: { type: Boolean, state: true },
supportBaseLink: { type: String },
};
static get queries() {
return {
formEl: "#password-inputs-form",
inputNewPasswordEl: "#new-password-input",
inputRepeatPasswordEl: "#repeat-password-input",
passwordRulesEl: "#password-rules",
};
}
constructor() {
super();
this.supportBaseLink = "";
this._tooShort = true;
this._hasCommon = false;
this._hasEmail = false;
this._passwordsMatch = false;
this._passwordsValid = false;
this._tooltipFocus = false;
}
reset() {
this.formEl.reset();
this._showRules = false;
this._hasCommon = false;
this._hasEmail = false;
this._tooShort = true;
this._passwordsMatch = false;
this._passwordsValid = false;
this._tooltipFocus = false;
}
handleFocusNewPassword() {
this._showRules = true;
}
handleBlurNewPassword(event) {
this._showRules = !event.target.checkValidity();
}
handleChangeNewPassword() {
this.updatePasswordValidity();
}
handleChangeRepeatPassword() {
this.updatePasswordValidity();
}
updatePasswordValidity() {
const emailRegex = /^[\w!#$%&'*+/=?^`{|}~.-]+@[A-Z0-9-]+\.[A-Z0-9.-]+$/i;
this._hasEmail = emailRegex.test(this.inputNewPasswordEl.value);
if (this._hasEmail) {
// TODO: we need a localized string for this error (bug 1909983)
this.inputNewPasswordEl.setCustomValidity("TODO: no emails");
} else {
this.inputNewPasswordEl.setCustomValidity("");
}
const newPassValidity = this.inputNewPasswordEl.validity;
this._tooShort = newPassValidity?.valueMissing || newPassValidity?.tooShort;
this._passwordsMatch =
this.inputNewPasswordEl.value == this.inputRepeatPasswordEl.value;
if (!this._passwordsMatch) {
// TODO: we need a localized string for this error (bug 1909983)
this.inputRepeatPasswordEl.setCustomValidity("TODO: not matching");
} else {
this.inputRepeatPasswordEl.setCustomValidity("");
}
const repeatPassValidity = this.inputRepeatPasswordEl.validity;
this._passwordsValid =
newPassValidity?.valid &&
repeatPassValidity?.valid &&
this._passwordsMatch;
/**
* This step may involve async validation with BackupService. For instance, we have to
* check against a list of common passwords (bug 1905140) and display an error message if an
* issue occurs (bug 1905145).
*/
}
handleTooltipFocus() {
this._tooltipFocus = true;
}
handleTooltipBlur() {
this._tooltipFocus = false;
}
/**
* Dispatches a custom event whenever validity changes.
*
* @param {Map<string, any>} 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`
<div id="password-inputs-wrapper" aria-live="polite">
<form id="password-inputs-form">
<!--TODO: (bug 1909983) change first input field label for the "change-password" dialog-->
<label id="new-password-label" for="new-password-input">
<div id="new-password-label-wrapper-span-input">
<span
id="new-password-span"
data-l10n-id="enable-backup-encryption-create-password-label"
></span>
<input
type="password"
id="new-password-input"
minlength="8"
required
@input=${this.handleChangeNewPassword}
@focus=${this.handleFocusNewPassword}
@blur=${this.handleBlurNewPassword}
/>
<!--TODO: (bug 1909984) improve how we read out the first input field for screen readers-->
</div>
</label>
<!--TODO: (bug 1909984) look into how the tooltip vs dialog behaves when pressing the ESC key-->
<password-rules-tooltip
id="password-rules"
class=${!this._showRules && !this._tooltipFocus ? "hidden" : ""}
.hasCommon=${this._hasCommon}
.hasEmail=${this._hasEmail}
.tooShort=${this._tooShort}
.supportBaseLink=${this.supportBaseLink}
@focus=${this.handleTooltipFocus}
@blur=${this.handleTooltipBlur}
></password-rules-tooltip>
<label id="repeat-password-label" for="repeat-password-input">
<span
id="repeat-password-span"
data-l10n-id="enable-backup-encryption-repeat-password-label"
></span>
<input
type="password"
id="repeat-password-input"
minlength="8"
required
@input=${this.handleChangeRepeatPassword}
/>
</label>
</form>
</div>
`;
}
render() {
return html`
<link
rel="stylesheet"
href="chrome://browser/content/backup/password-validation-inputs.css"
/>
${this.contentTemplate()}
`;
}
}
customElements.define("password-validation-inputs", PasswordValidationInputs);