diff --git a/browser/components/BrowserGlue.sys.mjs b/browser/components/BrowserGlue.sys.mjs index 7c699a3b16dc..e9bc72688dde 100644 --- a/browser/components/BrowserGlue.sys.mjs +++ b/browser/components/BrowserGlue.sys.mjs @@ -456,12 +456,14 @@ let JSWINDOWACTORS = { esModuleURI: "resource:///actors/BackupUIChild.sys.mjs", events: { "BackupUI:InitWidget": { wantUntrusted: true }, - "BackupUI:ToggleScheduledBackups": { wantUntrusted: true }, + "BackupUI:EnableScheduledBackups": { wantUntrusted: true }, + "BackupUI:DisableScheduledBackups": { wantUntrusted: true }, "BackupUI:ShowFilepicker": { wantUntrusted: true }, "BackupUI:GetBackupFileInfo": { wantUntrusted: true }, "BackupUI:RestoreFromBackupFile": { wantUntrusted: true }, "BackupUI:RestoreFromBackupChooseFile": { wantUntrusted: true }, - "BackupUI:ToggleEncryption": { wantUntrusted: true }, + "BackupUI:EnableEncryption": { wantUntrusted: true }, + "BackupUI:DisableEncryption": { wantUntrusted: true }, "BackupUI:RerunEncryption": { wantUntrusted: true }, "BackupUI:ShowBackupLocation": { wantUntrusted: true }, "BackupUI:EditBackupLocation": { wantUntrusted: true }, diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs index 40d5c0e7b483..e096b5c9aafb 100644 --- a/browser/components/backup/BackupService.sys.mjs +++ b/browser/components/backup/BackupService.sys.mjs @@ -2753,6 +2753,7 @@ export class BackupService extends EventTarget { lazy.logConsole.error( `Failed to set parent directory ${parentDirPath}. ${e}` ); + throw e; } } diff --git a/browser/components/backup/actors/BackupUIChild.sys.mjs b/browser/components/backup/actors/BackupUIChild.sys.mjs index 6fb760496a65..0de43d5dc8bd 100644 --- a/browser/components/backup/actors/BackupUIChild.sys.mjs +++ b/browser/components/backup/actors/BackupUIChild.sys.mjs @@ -29,8 +29,26 @@ export class BackupUIChild extends JSWindowActorChild { if (event.type == "BackupUI:InitWidget") { this.#inittedWidgets.add(event.target); this.sendAsyncMessage("RequestState"); - } else if (event.type == "BackupUI:ToggleScheduledBackups") { - this.sendAsyncMessage("ToggleScheduledBackups", event.detail); + } else if (event.type == "BackupUI:EnableScheduledBackups") { + const target = event.target; + + const result = await this.sendQuery( + "EnableScheduledBackups", + event.detail + ); + if (result.success) { + target.close(); + } else { + target.enableBackupErrorCode = result.errorCode; + } + } else if (event.type == "BackupUI:DisableScheduledBackups") { + const target = event.target; + + this.sendAsyncMessage("DisableScheduledBackups", event.detail); + // backups will always end up disabled even if there was an error + // with other bookkeeping related to turning off backups + + target.close(); } else if (event.type == "BackupUI:ShowFilepicker") { let targetNodeName = event.target.nodeName; let { path, filename, iconURL } = await this.sendQuery("ShowFilepicker", { @@ -79,10 +97,33 @@ export class BackupUIChild extends JSWindowActorChild { } } else if (event.type == "BackupUI:RestoreFromBackupChooseFile") { this.sendAsyncMessage("RestoreFromBackupChooseFile"); - } else if (event.type == "BackupUI:ToggleEncryption") { - this.sendAsyncMessage("ToggleEncryption", event.detail); + } else if (event.type == "BackupUI:EnableEncryption") { + const target = event.target; + + const result = await this.sendQuery("EnableEncryption", event.detail); + if (result.success) { + target.close(); + } else { + target.enableEncryptionErrorCode = result.errorCode; + } + } else if (event.type == "BackupUI:DisableEncryption") { + const target = event.target; + + const result = await this.sendQuery("DisableEncryption", event.detail); + if (result.success) { + target.close(); + } else { + target.disableEncryptionErrorCode = result.errorCode; + } } else if (event.type == "BackupUI:RerunEncryption") { - this.sendAsyncMessage("RerunEncryption", event.detail); + const target = event.target; + + const result = await this.sendQuery("RerunEncryption", event.detail); + if (result.success) { + target.close(); + } else { + target.rerunEncryptionErrorCode = result.errorCode; + } } else if (event.type == "BackupUI:ShowBackupLocation") { this.sendAsyncMessage("ShowBackupLocation"); } else if (event.type == "BackupUI:EditBackupLocation") { diff --git a/browser/components/backup/actors/BackupUIParent.sys.mjs b/browser/components/backup/actors/BackupUIParent.sys.mjs index 8389602f9f0f..571a94d457fd 100644 --- a/browser/components/backup/actors/BackupUIParent.sys.mjs +++ b/browser/components/backup/actors/BackupUIParent.sys.mjs @@ -82,50 +82,37 @@ export class BackupUIParent extends JSWindowActorParent { async receiveMessage(message) { if (message.name == "RequestState") { this.sendState(); - } else if (message.name == "ToggleScheduledBackups") { - let { isScheduledBackupsEnabled, parentDirPath, password } = message.data; - - if (isScheduledBackupsEnabled) { + } else if (message.name == "EnableScheduledBackups") { + try { + let { parentDirPath, password } = message.data; if (parentDirPath) { this.#bs.setParentDirPath(parentDirPath); - /** - * TODO: display an error and do not attempt to toggle scheduled backups if there - * is a problem with setting the parent directory (bug 1901308). - */ } - if (password) { - try { - await this.#bs.enableEncryption(password); - } catch (e) { - /** - * TODO: display en error and do not attempt to toggle scheduled backups if there is a - * problem with enabling encryption (bug 1901308) - */ - return null; - } - } - } else { - try { - if (this.#bs.state.encryptionEnabled) { - await this.#bs.disableEncryption(); - } - await this.#bs.deleteLastBackup(); - } catch (e) { - // no-op so that scheduled backups can still be turned off + await this.#bs.enableEncryption(password); } + this.#bs.setScheduledBackups(true); + } catch (e) { + lazy.logConsole.error(`Failed to enable scheduled backups`, e); + return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; } - - this.#bs.setScheduledBackups(isScheduledBackupsEnabled); - - return true; - /** * TODO: (Bug 1900125) we should create a backup at the specified dir path once we turn on * scheduled backups. The backup folder in the chosen directory should contain * the archive file, which we create using BackupService.createArchive implemented in * Bug 1897498. */ + return { success: true }; + } else if (message.name == "DisableScheduledBackups") { + try { + if (this.#bs.state.encryptionEnabled) { + await this.#bs.disableEncryption(); + } + await this.#bs.deleteLastBackup(); + } catch (e) { + // no-op so that scheduled backups can still be turned off + } + this.#bs.setScheduledBackups(false); } else if (message.name == "ShowFilepicker") { let { win, filter, displayDirectoryPath } = message.data; @@ -191,52 +178,45 @@ export class BackupUIParent extends JSWindowActorParent { return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; } return { success: true }; - } else if (message.name == "ToggleEncryption") { - let { isEncryptionEnabled, password } = message.data; - - if (!isEncryptionEnabled) { - try { - await this.#bs.disableEncryption(); - /** - * TODO: (Bug 1901640) after disabling encryption, recreate the backup, - * this time without sensitive data. - */ - } catch (e) { - /** - * TODO: (Bug 1901308) maybe display an error if there is a problem with - * disabling encryption. - */ - } - } else { - try { - await this.#bs.enableEncryption(password); - /** - * TODO: (Bug 1901640) after enabling encryption, recreate the backup, - * this time with sensitive data. - */ - } catch (e) { - /** - * TODO: (Bug 1901308) maybe display an error if there is a problem with - * enabling encryption. - */ - } + } else if (message.name == "EnableEncryption") { + try { + await this.#bs.enableEncryption(message.data.password); + } catch (e) { + lazy.logConsole.error(`Failed to enable encryption`, e); + return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; } - } else if (message.name == "RerunEncryption") { - let { password } = message.data; - + /** + * TODO: (Bug 1901640) after enabling encryption, recreate the backup, + * this time with sensitive data. + */ + return { success: true }; + } else if (message.name == "DisableEncryption") { try { await this.#bs.disableEncryption(); - await this.#bs.enableEncryption(password); - /** - * TODO: (Bug 1901640) after enabling encryption, recreate the backup, - * this time with the new password. - */ } catch (e) { - /** - * TODO: (Bug 1901308) maybe display an error if there is a problem with - * re-encryption. - */ + lazy.logConsole.error(`Failed to disable encryption`, e); + return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; } + /** + * TODO: (Bug 1901640) after disabling encryption, recreate the backup, + * this time without sensitive data. + */ + return { success: true }; + } else if (message.name == "RerunEncryption") { + try { + let { password } = message.data; + + await this.#bs.disableEncryption(); + await this.#bs.enableEncryption(password); + } catch (e) { + lazy.logConsole.error(`Failed to rerun encryption`, e); + return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN }; + } + /** + * TODO: (Bug 1901640) after enabling encryption, recreate the backup, + * this time with the new password. + */ + return { success: true }; } else if (message.name == "ShowBackupLocation") { this.#bs.showBackupLocation(); } else if (message.name == "EditBackupLocation") { diff --git a/browser/components/backup/content/backup-settings.mjs b/browser/components/backup/content/backup-settings.mjs index 79c0f437e8fb..211ab25bbb08 100644 --- a/browser/components/backup/content/backup-settings.mjs +++ b/browser/components/backup/content/backup-settings.mjs @@ -93,44 +93,14 @@ export default class BackupSettings extends MozLitElement { new CustomEvent("BackupUI:InitWidget", { bubbles: true }) ); - this.addEventListener("turnOnScheduledBackups", this); - this.addEventListener("turnOffScheduledBackups", this); this.addEventListener("dialogCancel", this); this.addEventListener("getBackupFileInfo", this); - this.addEventListener("enableEncryption", this); - this.addEventListener("rerunEncryption", this); - this.addEventListener("disableEncryption", this); this.addEventListener("restoreFromBackupConfirm", this); this.addEventListener("restoreFromBackupChooseFile", this); } handleEvent(event) { switch (event.type) { - case "turnOnScheduledBackups": - this.turnOnScheduledBackupsDialogEl.close(); - this.dispatchEvent( - new CustomEvent("BackupUI:ToggleScheduledBackups", { - bubbles: true, - composed: true, - detail: { - ...event.detail, - isScheduledBackupsEnabled: true, - }, - }) - ); - break; - case "turnOffScheduledBackups": - this.turnOffScheduledBackupsDialogEl.close(); - this.dispatchEvent( - new CustomEvent("BackupUI:ToggleScheduledBackups", { - bubbles: true, - composed: true, - detail: { - isScheduledBackupsEnabled: false, - }, - }) - ); - break; case "dialogCancel": if (this.turnOnScheduledBackupsDialogEl.open) { this.turnOnScheduledBackupsDialogEl.close(); @@ -175,43 +145,6 @@ export default class BackupSettings extends MozLitElement { }) ); break; - case "enableEncryption": - this.enableBackupEncryptionDialogEl.close(); - this.dispatchEvent( - new CustomEvent("BackupUI:ToggleEncryption", { - bubbles: true, - composed: true, - detail: { - ...event.detail, - isEncryptionEnabled: true, - }, - }) - ); - break; - case "rerunEncryption": - this.enableBackupEncryptionDialogEl.close(); - this.dispatchEvent( - new CustomEvent("BackupUI:RerunEncryption", { - bubbles: true, - composed: true, - detail: { - ...event.detail, - }, - }) - ); - break; - case "disableEncryption": - this.disableBackupEncryptionDialogEl.close(); - this.dispatchEvent( - new CustomEvent("BackupUI:ToggleEncryption", { - bubbles: true, - composed: true, - detail: { - isEncryptionEnabled: false, - }, - }) - ); - break; } } diff --git a/browser/components/backup/content/disable-backup-encryption.mjs b/browser/components/backup/content/disable-backup-encryption.mjs index fe4e1e4eaa89..bbf8e844976c 100644 --- a/browser/components/backup/content/disable-backup-encryption.mjs +++ b/browser/components/backup/content/disable-backup-encryption.mjs @@ -5,47 +5,66 @@ 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://global/content/elements/moz-message-bar.mjs"; + +const ERROR_L10N_ID = "backup-error-retry"; + /** * The widget for disabling password protection if the backup is already * encrypted. */ export default class DisableBackupEncryption extends MozLitElement { + static properties = { + // managed by BackupUIChild + disableEncryptionErrorCode: { type: Number }, + }; + static get queries() { return { cancelButtonEl: "#backup-disable-encryption-cancel-button", confirmButtonEl: "#backup-disable-encryption-confirm-button", + errorEl: "#disable-backup-encryption-error", }; } - /** - * Dispatches the BackupUI:InitWidget custom event upon being attached to the - * DOM, which registers with BackupUIChild for BackupService state updates. - */ - connectedCallback() { - super.connectedCallback(); - this.dispatchEvent( - new CustomEvent("BackupUI:InitWidget", { bubbles: true }) - ); + constructor() { + super(); + this.disableEncryptionErrorCode = 0; } - handleCancel() { + close() { this.dispatchEvent( new CustomEvent("dialogCancel", { bubbles: true, composed: true, }) ); + this.reset(); + } + + reset() { + this.disableEncryptionErrorCode = 0; } handleConfirm() { this.dispatchEvent( - new CustomEvent("disableEncryption", { + new CustomEvent("BackupUI:DisableEncryption", { bubbles: true, - composed: true, }) ); } + errorTemplate() { + return html` + + `; + } + contentTemplate() { return html`
+ ${this.disableEncryptionErrorCode ? this.errorTemplate() : null} html` +const Template = ({ disableEncryptionErrorCode }) => html` - + `; export const Default = Template.bind({}); + +export const DisableError = Template.bind({}); +DisableError.args = { + disableEncryptionErrorCode: ERRORS.UNKNOWN, +}; diff --git a/browser/components/backup/content/enable-backup-encryption.mjs b/browser/components/backup/content/enable-backup-encryption.mjs index 66f7b3942e6b..8e868434b2d5 100644 --- a/browser/components/backup/content/enable-backup-encryption.mjs +++ b/browser/components/backup/content/enable-backup-encryption.mjs @@ -5,9 +5,13 @@ import { html, ifDefined } 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://global/content/elements/moz-message-bar.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/backup/password-validation-inputs.mjs"; +import { ERRORS } from "chrome://browser/content/backup/backup-constants.mjs"; + /** * Valid attributes for the enable-backup-encryption dialog type. * @@ -23,14 +27,30 @@ const VALID_L10N_IDS = new Map([ [VALID_TYPES.CHANGE_PASSWORD, "change-backup-encryption-header"], ]); +const ERROR_L10N_IDS = Object.freeze({ + [ERRORS.INVALID_PASSWORD]: "backup-error-password-requirements", + [ERRORS.UNKNOWN]: "backup-error-retry", +}); + +/** + * @param {number} errorCode Error code from backup-constants.mjs + * @returns {string} Localization ID for error message + */ +function getErrorL10nId(errorCode) { + return ERROR_L10N_IDS[errorCode] ?? ERROR_L10N_IDS[ERRORS.UNKNOWN]; +} + /** * The widget for enabling password protection if the backup is not yet * encrypted. */ export default class EnableBackupEncryption extends MozLitElement { static properties = { + // internal state _inputPassValue: { type: String, state: true }, _passwordsMatch: { type: Boolean, state: true }, + + // passed from parents supportBaseLink: { type: String }, /** * The "type" attribute changes the layout. @@ -38,6 +58,10 @@ export default class EnableBackupEncryption extends MozLitElement { * @see VALID_TYPES */ type: { type: String, reflect: true }, + + // managed by BackupUIChild + enableEncryptionErrorCode: { type: Number }, + rerunEncryptionErrorCode: { type: Number }, }; static get queries() { @@ -48,6 +72,7 @@ export default class EnableBackupEncryption extends MozLitElement { textHeaderEl: "#backup-enable-encryption-header", textDescriptionEl: "#backup-enable-encryption-description", passwordInputsEl: "#backup-enable-encryption-password-inputs", + errorEl: "#enable-backup-encryption-error", }; } @@ -57,18 +82,13 @@ export default class EnableBackupEncryption extends MozLitElement { this.type = VALID_TYPES.SET_PASSWORD; this._inputPassValue = ""; this._passwordsMatch = false; + this.enableEncryptionErrorCode = 0; + this.rerunEncryptionErrorCode = 0; } - /** - * Dispatches the BackupUI:InitWidget custom event upon being attached to the - * DOM, which registers with BackupUIChild for BackupService state updates. - */ connectedCallback() { super.connectedCallback(); - this.dispatchEvent( - new CustomEvent("BackupUI:InitWidget", { bubbles: true }) - ); - + // Listening to events from child this.addEventListener("ValidPasswordsDetected", this); this.addEventListener("InvalidPasswordsDetected", this); } @@ -84,23 +104,29 @@ export default class EnableBackupEncryption extends MozLitElement { } } - handleCancel() { + close() { this.dispatchEvent( new CustomEvent("dialogCancel", { bubbles: true, composed: true, }) ); - this.resetChanges(); + this.reset(); + } + + reset() { + this._inputPassValue = ""; + this._passwordsMatch = false; + this.passwordInputsEl.reset(); + this.enableEncryptionErrorCode = 0; } handleConfirm() { switch (this.type) { case VALID_TYPES.SET_PASSWORD: this.dispatchEvent( - new CustomEvent("enableEncryption", { + new CustomEvent("BackupUI:EnableEncryption", { bubbles: true, - composed: true, detail: { password: this._inputPassValue, }, @@ -109,9 +135,8 @@ export default class EnableBackupEncryption extends MozLitElement { break; case VALID_TYPES.CHANGE_PASSWORD: this.dispatchEvent( - new CustomEvent("rerunEncryption", { + new CustomEvent("BackupUI:RerunEncryption", { bubbles: true, - composed: true, detail: { password: this._inputPassValue, }, @@ -119,16 +144,6 @@ export default class EnableBackupEncryption extends MozLitElement { ); break; } - this.resetChanges(); - } - - resetChanges() { - this._inputPassValue = ""; - this._passwordsMatch = false; - - this.passwordInputsEl.dispatchEvent( - new CustomEvent("resetInputs", { bubbles: true, composed: true }) - ); } descriptionTemplate() { @@ -155,7 +170,7 @@ export default class EnableBackupEncryption extends MozLitElement { + `; + } + contentTemplate() { return html`
+ + ${this.enableEncryptionErrorCode || this.rerunEncryptionErrorCode + ? this.errorTemplate() + : null}
${this.buttonGroupTemplate()} diff --git a/browser/components/backup/content/enable-backup-encryption.stories.mjs b/browser/components/backup/content/enable-backup-encryption.stories.mjs index e892d0380cab..3fc697d0904d 100644 --- a/browser/components/backup/content/enable-backup-encryption.stories.mjs +++ b/browser/components/backup/content/enable-backup-encryption.stories.mjs @@ -5,11 +5,17 @@ // eslint-disable-next-line import/no-unresolved import { html } from "lit.all.mjs"; import "chrome://global/content/elements/moz-card.mjs"; +import { ERRORS } from "chrome://browser/content/backup/backup-constants.mjs"; import "./enable-backup-encryption.mjs"; window.MozXULElement.insertFTLIfNeeded("locales-preview/backupSettings.ftl"); window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl"); +const SELECTABLE_ERRORS = { + "(none)": 0, + ...ERRORS, +}; + export default { title: "Domain-specific UI Widgets/Backup/Enable Encryption", component: "enable-backup-encryption", @@ -18,12 +24,30 @@ export default { control: { type: "select" }, options: ["set-password", "change-password"], }, + enableEncryptionErrorCode: { + options: Object.keys(SELECTABLE_ERRORS), + mapping: SELECTABLE_ERRORS, + control: { type: "select" }, + }, + rerunEncryptionErrorCode: { + options: Object.keys(SELECTABLE_ERRORS), + mapping: SELECTABLE_ERRORS, + control: { type: "select" }, + }, }, }; -const Template = ({ type }) => html` +const Template = ({ + type, + enableEncryptionErrorCode, + rerunEncryptionErrorCode, +}) => html` - + `; @@ -36,3 +60,15 @@ export const ChangePassword = Template.bind({}); ChangePassword.args = { type: "change-password", }; + +export const SetPasswordError = Template.bind({}); +SetPasswordError.args = { + type: "set-password", + enableEncryptionErrorCode: ERRORS.INVALID_PASSWORD, +}; + +export const ChangePasswordError = Template.bind({}); +ChangePasswordError.args = { + type: "change-password", + rerunEncryptionErrorCode: ERRORS.INVALID_PASSWORD, +}; diff --git a/browser/components/backup/content/password-validation-inputs.mjs b/browser/components/backup/content/password-validation-inputs.mjs index 4f7171e91b66..46628c97ab35 100644 --- a/browser/components/backup/content/password-validation-inputs.mjs +++ b/browser/components/backup/content/password-validation-inputs.mjs @@ -48,17 +48,7 @@ export default class PasswordValidationInputs extends MozLitElement { this._tooltipFocus = false; } - connectedCallback() { - super.connectedCallback(); - this.addEventListener("resetInputs", this.handleReset); - } - - disconnectedCallback() { - super.disconnectedCallback(); - this.removeEventListener("resetInputs", this.handleReset); - } - - handleReset() { + reset() { this.formEl.reset(); this._showRules = false; this._hasCommon = false; diff --git a/browser/components/backup/content/turn-off-scheduled-backups.mjs b/browser/components/backup/content/turn-off-scheduled-backups.mjs index d6731fc69530..c48323b835d9 100644 --- a/browser/components/backup/content/turn-off-scheduled-backups.mjs +++ b/browser/components/backup/content/turn-off-scheduled-backups.mjs @@ -17,18 +17,7 @@ export default class TurnOffScheduledBackups extends MozLitElement { }; } - /** - * Dispatches the BackupUI:InitWidget custom event upon being attached to the - * DOM, which registers with BackupUIChild for BackupService state updates. - */ - connectedCallback() { - super.connectedCallback(); - this.dispatchEvent( - new CustomEvent("BackupUI:InitWidget", { bubbles: true }) - ); - } - - handleCancel() { + close() { this.dispatchEvent( new CustomEvent("dialogCancel", { bubbles: true, @@ -39,9 +28,8 @@ export default class TurnOffScheduledBackups extends MozLitElement { handleConfirm() { this.dispatchEvent( - new CustomEvent("turnOffScheduledBackups", { + new CustomEvent("BackupUI:DisableScheduledBackups", { bubbles: true, - composed: true, }) ); } @@ -77,7 +65,7 @@ export default class TurnOffScheduledBackups extends MozLitElement { this.addEventListener("ValidPasswordsDetected", this); this.addEventListener("InvalidPasswordsDetected", this); } @@ -94,14 +121,14 @@ export default class TurnOnScheduledBackups extends MozLitElement { ); } - handleCancel() { + close() { this.dispatchEvent( new CustomEvent("dialogCancel", { bubbles: true, composed: true, }) ); - this.resetChanges(); + this.reset(); } handleConfirm() { @@ -114,13 +141,11 @@ export default class TurnOnScheduledBackups extends MozLitElement { } this.dispatchEvent( - new CustomEvent("turnOnScheduledBackups", { + new CustomEvent("BackupUI:EnableScheduledBackups", { bubbles: true, - composed: true, detail, }) ); - this.resetChanges(); } handleTogglePasswordOptions() { @@ -128,7 +153,7 @@ export default class TurnOnScheduledBackups extends MozLitElement { this._passwordsMatch = false; } - resetChanges() { + reset() { this._newPath = ""; this._newIconURL = ""; this._newLabel = ""; @@ -136,11 +161,12 @@ export default class TurnOnScheduledBackups extends MozLitElement { this.passwordOptionsCheckboxEl.checked = false; this._passwordsMatch = false; this._inputPassValue = ""; + this.enableBackupErrorCode = 0; if (this.passwordOptionsExpandedEl) { - this.passwordOptionsExpandedEl.dispatchEvent( - new CustomEvent("resetInputs", { bubbles: true, composed: true }) - ); + /** @type {import("./password-validation-inputs.mjs").default} */ + const passwordElement = this.passwordOptionsExpandedEl; + passwordElement.reset(); } } @@ -180,6 +206,16 @@ export default class TurnOnScheduledBackups extends MozLitElement { `; } + errorTemplate() { + return html` + + `; + } + allOptionsTemplate() { return html`
@@ -269,12 +305,13 @@ export default class TurnOnScheduledBackups extends MozLitElement { > ${this.allOptionsTemplate()} + ${this.enableBackupErrorCode ? this.errorTemplate() : null} html` +const Template = ({ + defaultPath, + _newPath, + defaultLabel, + _newLabel, + enableBackupErrorCode, +}) => html` `; @@ -39,3 +58,9 @@ CustomLocation.args = { _newPath: "/Some/Test/Custom/Dir", _newLabel: "Dir", }; + +export const EnableError = Template.bind({}); +EnableError.args = { + ...CustomLocation.args, + enableBackupErrorCode: ERRORS.FILE_SYSTEM_ERROR, +}; diff --git a/browser/components/backup/tests/browser/browser_settings.js b/browser/components/backup/tests/browser/browser_settings.js index 48551b4794d3..1a2fc6e37621 100644 --- a/browser/components/backup/tests/browser/browser_settings.js +++ b/browser/components/backup/tests/browser/browser_settings.js @@ -99,7 +99,10 @@ add_task(async function test_disable_backup_encryption_confirm() { ); let confirmButton = disableBackupEncryption.confirmButtonEl; - let promise = BrowserTestUtils.waitForEvent(window, "disableEncryption"); + let promise = BrowserTestUtils.waitForEvent( + window, + "BackupUI:DisableEncryption" + ); Assert.ok(confirmButton, "Confirm button should be found"); diff --git a/browser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js b/browser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js index fd5fd0250508..43e9ba4153c3 100644 --- a/browser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js +++ b/browser/components/backup/tests/browser/browser_settings_enable_backup_encryption.js @@ -91,7 +91,7 @@ add_task(async function test_enable_backup_encryption_checkbox_confirm() { let encryptionPromise = BrowserTestUtils.waitForEvent( window, - "enableEncryption" + "BackupUI:EnableEncryption" ); confirmButton.click(); @@ -185,7 +185,10 @@ add_task( await settings.updateComplete; confirmButton = settings.enableBackupEncryptionEl.confirmButtonEl; - let promise = BrowserTestUtils.waitForEvent(window, "rerunEncryption"); + let promise = BrowserTestUtils.waitForEvent( + window, + "BackupUI:RerunEncryption" + ); confirmButton.click(); await promise; diff --git a/browser/components/backup/tests/browser/browser_settings_turn_off_scheduled_backups.js b/browser/components/backup/tests/browser/browser_settings_turn_off_scheduled_backups.js index aa5f595ffe0c..bb91ecb4bfbf 100644 --- a/browser/components/backup/tests/browser/browser_settings_turn_off_scheduled_backups.js +++ b/browser/components/backup/tests/browser/browser_settings_turn_off_scheduled_backups.js @@ -37,7 +37,7 @@ async function turnOffScheduledBackupsHelper(browser, taskFn) { let confirmButton = turnOffScheduledBackups.confirmButtonEl; let promise = BrowserTestUtils.waitForEvent( window, - "turnOffScheduledBackups" + "BackupUI:DisableScheduledBackups" ); Assert.ok(confirmButton, "Confirm button should be found"); diff --git a/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js b/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js index ad61d31265cf..91ecaf2566e0 100644 --- a/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js +++ b/browser/components/backup/tests/browser/browser_settings_turn_on_scheduled_backups.js @@ -3,6 +3,10 @@ "use strict"; +const { ERRORS } = ChromeUtils.importESModule( + "chrome://browser/content/backup/backup-constants.mjs" +); + const SCHEDULED_BACKUPS_ENABLED_PREF = "browser.backup.scheduled.enabled"; add_setup(async () => { @@ -43,7 +47,7 @@ add_task(async function test_turn_on_scheduled_backups_confirm() { let confirmButton = turnOnScheduledBackups.confirmButtonEl; let promise = BrowserTestUtils.waitForEvent( window, - "turnOnScheduledBackups" + "BackupUI:EnableScheduledBackups" ); Assert.ok(confirmButton, "Confirm button should be found"); @@ -86,6 +90,7 @@ add_task(async function test_turn_on_custom_location_filepicker() { MockFilePicker.returnValue = MockFilePicker.returnOK; // After setting up mocks, start testing components + /** @type {import("../../content/backup-settings.mjs").default} */ let settings = browser.contentDocument.querySelector("backup-settings"); let turnOnButton = settings.scheduledBackupsButtonEl; @@ -148,7 +153,7 @@ add_task(async function test_turn_on_custom_location_filepicker() { let confirmButtonPromise = BrowserTestUtils.waitForEvent( window, - "turnOnScheduledBackups" + "BackupUI:EnableScheduledBackups" ); confirmButton.click(); @@ -234,7 +239,7 @@ add_task(async function test_turn_on_scheduled_backups_encryption() { let promise = BrowserTestUtils.waitForEvent( window, - "turnOnScheduledBackups" + "BackupUI:EnableScheduledBackups" ); confirmButton.click(); @@ -280,7 +285,7 @@ add_task(async function test_turn_on_scheduled_backups_encryption_error() { let encryptionStub = sandbox .stub(BackupService.prototype, "enableEncryption") - .throws(); + .throws(new Error("test error", { cause: ERRORS.INVALID_PASSWORD })); // Enable passwords let passwordsCheckbox = turnOnScheduledBackups.passwordOptionsCheckboxEl; @@ -317,7 +322,7 @@ add_task(async function test_turn_on_scheduled_backups_encryption_error() { let promise = BrowserTestUtils.waitForEvent( window, - "turnOnScheduledBackups" + "BackupUI:EnableScheduledBackups" ); confirmButton.click(); @@ -339,6 +344,16 @@ add_task(async function test_turn_on_scheduled_backups_encryption_error() { "Scheduled backups pref should still be false" ); + await BrowserTestUtils.waitForCondition( + () => !!turnOnScheduledBackups.errorEl, + "Error should be displayed to the user" + ); + + Assert.ok( + turnOnScheduledBackups.errorEl, + "Error should be displayed to the user" + ); + sandbox.restore(); Services.prefs.clearUserPref(SCHEDULED_BACKUPS_ENABLED_PREF); }); diff --git a/browser/components/backup/tests/browser/head.js b/browser/components/backup/tests/browser/head.js index 5febf1edc31b..12ceb20a6208 100644 --- a/browser/components/backup/tests/browser/head.js +++ b/browser/components/backup/tests/browser/head.js @@ -9,6 +9,7 @@ const { BackupService } = ChromeUtils.importESModule( const { MockFilePicker } = SpecialPowers; +/** @type {{sinon: import("@types/sinon").SinonApi}} */ const { sinon } = ChromeUtils.importESModule( "resource://testing-common/Sinon.sys.mjs" ); diff --git a/browser/components/backup/tests/chrome/test_disable_backup_encryption.html b/browser/components/backup/tests/chrome/test_disable_backup_encryption.html index cb0c1a8eb799..184fed2e12b5 100644 --- a/browser/components/backup/tests/chrome/test_disable_backup_encryption.html +++ b/browser/components/backup/tests/chrome/test_disable_backup_encryption.html @@ -16,22 +16,6 @@ "resource://testing-common/BrowserTestUtils.sys.mjs" ); - /** - * Tests that adding a disable-backup-encryption element to the DOM causes it to - * fire a BackupUI:InitWidget event. - */ - add_task(async function test_initWidget() { - let disableBackupEncryption = document.createElement("disable-backup-encryption"); - let content = document.getElementById("content"); - - let sawInitWidget = BrowserTestUtils.waitForEvent(content, "BackupUI:InitWidget"); - content.appendChild(disableBackupEncryption); - await sawInitWidget; - ok(true, "Saw BackupUI:InitWidget"); - - disableBackupEncryption.remove(); - }); - /** * Tests that pressing the confirm button will dispatch the expected events. */ @@ -42,7 +26,7 @@ ok(confirmButton, "Confirm button should be found"); let content = document.getElementById("content"); - let promise = BrowserTestUtils.waitForEvent(content, "disableEncryption"); + let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:DisableEncryption"); confirmButton.click() diff --git a/browser/components/backup/tests/chrome/test_enable_backup_encryption.html b/browser/components/backup/tests/chrome/test_enable_backup_encryption.html index e39dda651d50..919f56a63e33 100644 --- a/browser/components/backup/tests/chrome/test_enable_backup_encryption.html +++ b/browser/components/backup/tests/chrome/test_enable_backup_encryption.html @@ -17,22 +17,6 @@ "resource://testing-common/BrowserTestUtils.sys.mjs" ); - /** - * Tests that adding a enable-backup-encryption element to the DOM causes it to - * fire a BackupUI:InitWidget event. - */ - add_task(async function test_initWidget() { - let enableBackupEncryption = document.createElement("enable-backup-encryption"); - let content = document.getElementById("content"); - - let sawInitWidget = BrowserTestUtils.waitForEvent(content, "BackupUI:InitWidget"); - content.appendChild(enableBackupEncryption); - await sawInitWidget; - ok(true, "Saw BackupUI:InitWidget"); - - enableBackupEncryption.remove(); - }); - /** * Tests that pressing the confirm button for the set-password type dialog will dispatch the expected events. */ @@ -61,12 +45,14 @@ ok(!confirmButton.disabled, "Confirm button should no longer be disabled"); let content = document.getElementById("content"); - let encryptionPromise = BrowserTestUtils.waitForEvent(content, "enableEncryption"); + let encryptionPromise = BrowserTestUtils.waitForEvent(content, "BackupUI:EnableEncryption"); confirmButton.click() await encryptionPromise; ok(true, "Detected event after selecting the confirm button"); + + enableBackupEncryption.reset(); }) /** @@ -99,12 +85,14 @@ ok(!confirmButton.disabled, "Confirm button should no longer be disabled"); let content = document.getElementById("content"); - let encryptionPromise = BrowserTestUtils.waitForEvent(content, "rerunEncryption"); + let encryptionPromise = BrowserTestUtils.waitForEvent(content, "BackupUI:RerunEncryption"); confirmButton.click() await encryptionPromise; ok(true, "Detected event after selecting the confirm button"); + + enableBackupEncryption.reset(); }) /** @@ -126,6 +114,8 @@ await promise; ok(true, "Detected event after selecting the cancel button"); + + enableBackupEncryption.reset(); }) diff --git a/browser/components/backup/tests/chrome/test_turn_off_scheduled_backups.html b/browser/components/backup/tests/chrome/test_turn_off_scheduled_backups.html index a92422fcfd7b..266fc46dceb3 100644 --- a/browser/components/backup/tests/chrome/test_turn_off_scheduled_backups.html +++ b/browser/components/backup/tests/chrome/test_turn_off_scheduled_backups.html @@ -16,22 +16,6 @@ "resource://testing-common/BrowserTestUtils.sys.mjs" ); - /** - * Tests that adding a turn-off-scheduled-backups element to the DOM causes it to - * fire a BackupUI:InitWidget event. - */ - add_task(async function test_initWidget() { - let turnOffScheduledBackups = document.createElement("turn-off-scheduled-backups"); - let content = document.getElementById("content"); - - let sawInitWidget = BrowserTestUtils.waitForEvent(content, "BackupUI:InitWidget"); - content.appendChild(turnOffScheduledBackups); - await sawInitWidget; - ok(true, "Saw BackupUI:InitWidget"); - - turnOffScheduledBackups.remove(); - }); - /** * Tests that pressing the confirm button will dispatch the expected events. */ @@ -42,7 +26,7 @@ ok(confirmButton, "Confirm button should be found"); let content = document.getElementById("content"); - let promise = BrowserTestUtils.waitForEvent(content, "turnOffScheduledBackups"); + let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:DisableScheduledBackups"); confirmButton.click() diff --git a/browser/components/backup/tests/chrome/test_turn_on_scheduled_backups.html b/browser/components/backup/tests/chrome/test_turn_on_scheduled_backups.html index 57270ae2ab0a..38d407cb1415 100644 --- a/browser/components/backup/tests/chrome/test_turn_on_scheduled_backups.html +++ b/browser/components/backup/tests/chrome/test_turn_on_scheduled_backups.html @@ -45,7 +45,7 @@ ok(confirmButton, "Confirm button should be found"); let content = document.getElementById("content"); - let promise = BrowserTestUtils.waitForEvent(content, "turnOnScheduledBackups"); + let promise = BrowserTestUtils.waitForEvent(content, "BackupUI:EnableScheduledBackups"); confirmButton.click() diff --git a/browser/locales-preview/backupSettings.ftl b/browser/locales-preview/backupSettings.ftl index 44191a78458a..2abc29dbd29f 100644 --- a/browser/locales-preview/backupSettings.ftl +++ b/browser/locales-preview/backupSettings.ftl @@ -81,6 +81,10 @@ turn-on-scheduled-backups-encryption-repeat-password-label = Repeat password turn-on-scheduled-backups-cancel-button = Cancel turn-on-scheduled-backups-confirm-button = Turn on backup +# Tell the user there was an error accessing the user's selected backup +# folder. The folder may be invalid or inaccessible. +turn-on-scheduled-backups-error-file-system = There was a problem with your selected backup folder. Choose a different folder and try again. + ## These strings are displayed in a modal when users want to turn off scheduled backups. turn-off-scheduled-backups-header = Turn off backup? @@ -195,6 +199,17 @@ disable-backup-encryption-support-link = What will be backed up? disable-backup-encryption-cancel-button = Cancel disable-backup-encryption-confirm-button = Remove password +## These strings are used to tell users when errors occur when using +## the backup system + +backup-error-password-requirements = Your password doesn’t meet the requirements. Please try another password. + +# This error message will be shown to the user when something went wrong with +# the backup system but we do not have any more specific idea of what went +# wrong. This message invites the user to try an action again because there +# is a chance that the action will succeed if retried. +backup-error-retry = Something went wrong. Please try again. + ## These strings are inserted into the generated single-file backup archive. ## The single-file backup archive is a specially-crafted, static HTML file ## that is placed within a user specified directory (the Documents folder by