r, // and once each time one of our reactive properties is changed. if (this.aboutWelcomeEmbedded) { this.resizeTextarea(); } if (changedProperties.has("backupServiceState")) { // If we got a recovery error, recoveryInProgress should be false const inProgress = this.backupServiceState.recoveryInProgress && !this.backupServiceState.recoveryErrorCode; this.dispatchEvent( new CustomEvent("BackupUI:RecoveryProgress", { bubbles: true, composed: true, detail: { recoveryInProgress: inProgress }, }) ); // It's possible that backupFileToRestore got updated and we need to // refetch the fileInfo this.maybeGetBackupFileInfo(); } } handleEvent(event) { if (event.type == "BackupUI:SelectNewFilepickerPath") { let { path, iconURL } = event.detail; this._fileIconURL = iconURL; this.#backupFileReadPromise = Promise.withResolvers(); this.#backupFileReadPromise.promise.then(() => { const payload = { location: this.backupServiceState?.backupFileCoarseLocation, valid: this.backupServiceState?.recoveryErrorCode == ERRORS.NONE, }; if (payload.valid) { payload.backup_timestamp = new Date( this.backupServiceState?.backupFileInfo?.date || 0 ).getTime(); payload.restore_id = this.backupServiceState?.restoreID; payload.encryption = this.backupServiceState?.backupFileInfo?.isEncrypted; payload.app_name = this.backupServiceState?.backupFileInfo?.appName; payload.version = this.backupServiceState?.backupFileInfo?.appVersion; payload.build_id = this.backupServiceState?.backupFileInfo?.buildID; payload.os_name = this.backupServiceState?.backupFileInfo?.osName; payload.os_version = this.backupServiceState?.backupFileInfo?.osVersion; payload.telemetry_enabled = this.backupServiceState?.backupFileInfo?.healthTelemetryEnabled; } Glean.browserBackup.restoreFileChosen.record(payload); Services.obs.notifyObservers(null, "browser-backup-glean-sent"); }); this.getBackupFileInfo(path); } else if (event.type == "BackupUI:StateWasUpdated") { this.#initializedResolvers.resolve(); if (this.#backupFileReadPromise) { this.#backupFileReadPromise.resolve(); this.#backupFileReadPromise = null; } } } handleChooseBackupFile() { this.dispatchEvent( new CustomEvent("BackupUI:ShowFilepicker", { bubbles: true, composed: true, detail: { win: window.browsingContext, filter: "filterHTML", existingBackupPath: this.backupServiceState?.backupFileToRestore, }, }) ); } getBackupFileInfo(pathToFile = null) { let backupFile = pathToFile || this.backupServiceState?.backupFileToRestore; if (!backupFile) { return; } this.dispatchEvent( new CustomEvent("BackupUI:GetBackupFileInfo", { bubbles: true, composed: true, detail: { backupFile, }, }) ); } handleCancel() { this.dispatchEvent( new CustomEvent("dialogCancel", { bubbles: true, composed: true, }) ); } handleConfirm() { let backupFile = this.backupServiceState?.backupFileToRestore; if (!backupFile || this.backupServiceState?.recoveryInProgress) { return; } let backupPassword = this.passwordInput?.value; this.dispatchEvent( new CustomEvent("BackupUI:RestoreFromBackupFile", { bubbles: true, composed: true, detail: { backupFile, backupPassword, }, }) ); } handleTextareaResize() { this.resizeTextarea(); } /** * Resizes the textarea to adjust to the size of the content within */ resizeTextarea() { const target = this.filePicker; if (!target) { return; } const hasValue = target.value && !!target.value.trim().length; target.style.height = "auto"; if (hasValue) { target.style.height = target.scrollHeight + "px"; } } /** * Constructs a support URL with UTM parameters for use * when embedded in about:welcome * * @param {string} supportPage - The support page slug * @returns {string} The full support URL including UTM params */ getSupportURLWithUTM(supportPage) { let supportURL = new URL( supportPage, this.backupServiceState.supportBaseLink ); supportURL.searchParams.set("utm_medium", "firefox-desktop"); supportURL.searchParams.set("utm_source", "npo"); supportURL.searchParams.set("utm_campaign", "fx-backup-restore"); supportURL.searchParams.set("utm_content", "restore-error"); return supportURL.href; } /** * Returns a support link anchor element, either with UTM params for use in * about:welcome, or falling back to moz-support-link otherwise * * @param {object} options - Link configuration options * @param {string} options.id - The element id * @param {string} options.l10nId - The fluent l10n id * @param {string} options.l10nName - The fluent l10n name * @param {string} options.supportPage - The support page slug * @returns {TemplateResult} The link template */ getSupportLinkAnchor({ id, l10nId, l10nName, supportPage = "firefox-backup", }) { if (this.aboutWelcomeEmbedded) { return html``; } return html``; } applyContentCustomizations() { if (this.aboutWelcomeEmbedded) { this.style.setProperty( "--label-font-weight", "var(--font-weight-semibold)" ); } } renderBackupFileInfo(backupFileInfo) { return html`

`; } renderBackupFileStatus() { const { backupFileInfo, recoveryErrorCode } = this.backupServiceState || {}; // We have errors and are embedded in about:welcome if ( recoveryErrorCode && !this.isIncorrectPassword && this.aboutWelcomeEmbedded ) { return this.genericFileErrorTemplate(); } // No backup file selected if (!backupFileInfo) { return this.getSupportLinkAnchor({ id: "restore-from-backup-no-backup-file-link", l10nId: "restore-from-backup-no-backup-file-link", }); } // Backup file found and no error return this.renderBackupFileInfo(backupFileInfo); } controlsTemplate() { let iconURL = this.#placeholderFileIconURL; if ( this.backupServiceState?.backupFileToRestore && !this.aboutWelcomeEmbedded ) { iconURL = this._fileIconURL || this.#placeholderFileIconURL; } return html`
${this.inputTemplate(iconURL)}
${this.renderBackupFileStatus()}
${this.backupServiceState?.backupFileInfo?.isEncrypted ? this.passwordEntryTemplate() : null}
`; } inputTemplate(iconURL) { const styles = styleMap( iconURL ? { backgroundImage: `url(${iconURL})` } : {} ); const backupFileName = this.backupServiceState?.backupFileToRestore || ""; // Determine the ID of the element that will be rendered by renderBackupFileStatus() // to reference with aria-describedby let describedBy = ""; const { backupFileInfo, recoveryErrorCode } = this.backupServiceState || {}; if (this.aboutWelcomeEmbedded) { if (recoveryErrorCode && !this.isIncorrectPassword) { describedBy = "backup-generic-file-error"; } else if (!backupFileInfo) { describedBy = "restore-from-backup-no-backup-file-link"; } else { describedBy = "restore-from-backup-backup-found-info"; } } if (this.aboutWelcomeEmbedded) { return html` `; } return html` `; } passwordEntryTemplate() { const isInvalid = this.isIncorrectPassword; const describedBy = isInvalid ? "backup-password-error" : "backup-password-description"; return html`
${isInvalid ? html` ${this.getSupportLinkAnchor({ id: "backup-incorrect-password-support-link", l10nName: "incorrect-password-support-link", })} ` : html` `}
`; } contentTemplate() { let buttonL10nId = !this.backupServiceState?.recoveryInProgress ? "restore-from-backup-confirm-button" : "restore-from-backup-restoring-button"; return html`
${this.aboutWelcomeEmbedded ? null : this.headerTemplate()}
${!this.aboutWelcomeEmbedded && this.backupServiceState?.recoveryErrorCode ? this.errorTemplate() : null} ${!this.aboutWelcomeEmbedded && this.backupServiceState?.backupFileInfo ? this.descriptionTemplate() : null} ${this.controlsTemplate()}
${this.aboutWelcomeEmbedded ? null : this.cancelButtonTemplate()}
`; } headerTemplate() { return html`

`; } cancelButtonTemplate() { return html` `; } descriptionTemplate() { let { date } = this.backupServiceState?.backupFileInfo || {}; let dateTime = date && new Date(date).getTime(); return html` `; } errorTemplate() { // We handle incorrect password errors in the password input if (this.isIncorrectPassword) { return null; } return html` `; } genericFileErrorTemplate() { // We handle incorrect password errors in the password input if (this.isIncorrectPassword) { return null; } return html` `; } render() { this.applyContentCustomizations(); return html` ${this.contentTemplate()} `; } } customElements.define("restore-from-backup", RestoreFromBackup); PK