backup-settings.mjs (18718B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { html } from "chrome://global/content/vendor/lit.all.mjs"; 6 import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; 7 import { getErrorL10nId } from "chrome://browser/content/backup/backup-errors.mjs"; 8 import { ERRORS } from "chrome://browser/content/backup/backup-constants.mjs"; 9 10 // eslint-disable-next-line import/no-unassigned-import 11 import "chrome://browser/content/backup/turn-on-scheduled-backups.mjs"; 12 // eslint-disable-next-line import/no-unassigned-import 13 import "chrome://browser/content/backup/turn-off-scheduled-backups.mjs"; 14 // eslint-disable-next-line import/no-unassigned-import 15 import "chrome://browser/content/backup/restore-from-backup.mjs"; 16 // eslint-disable-next-line import/no-unassigned-import 17 import "chrome://browser/content/backup/enable-backup-encryption.mjs"; 18 // eslint-disable-next-line import/no-unassigned-import 19 import "chrome://browser/content/backup/disable-backup-encryption.mjs"; 20 21 /** 22 * The widget for managing the BackupService that is embedded within the main 23 * document of about:settings / about:preferences. 24 */ 25 export default class BackupSettings extends MozLitElement { 26 #placeholderIconURL = "chrome://global/skin/icons/page-portrait.svg"; 27 inProgressTimeout = null; 28 showInProgress = false; 29 30 // Decides how long the progress message bar persists for 31 MESSAGE_BAR_BUFFER = 3000; 32 33 static properties = { 34 backupServiceState: { type: Object }, 35 _enableEncryptionTypeAttr: { type: String }, 36 }; 37 38 static get queries() { 39 return { 40 scheduledBackupsButtonEl: "#backup-toggle-scheduled-button", 41 archiveSectionEl: "#scheduled-backups", 42 restoreSectionEl: "#restore-from-backup", 43 triggerBackupButtonEl: "#backup-trigger-button", 44 changePasswordButtonEl: "#backup-change-password-button", 45 disableBackupEncryptionEl: "disable-backup-encryption", 46 disableBackupEncryptionDialogEl: "#disable-backup-encryption-dialog", 47 enableBackupEncryptionEl: "enable-backup-encryption", 48 enableBackupEncryptionDialogEl: "#enable-backup-encryption-dialog", 49 turnOnScheduledBackupsDialogEl: "#turn-on-scheduled-backups-dialog", 50 turnOnScheduledBackupsEl: "turn-on-scheduled-backups", 51 turnOffScheduledBackupsEl: "turn-off-scheduled-backups", 52 turnOffScheduledBackupsDialogEl: "#turn-off-scheduled-backups-dialog", 53 restoreFromBackupEl: "restore-from-backup", 54 restoreFromBackupButtonEl: "#backup-toggle-restore-button", 55 restoreFromBackupDescriptionEl: "#backup-restore-description", 56 restoreFromBackupDialogEl: "#restore-from-backup-dialog", 57 sensitiveDataCheckboxInputEl: "#backup-sensitive-data-checkbox-input", 58 passwordControlsEl: "#backup-password-controls", 59 lastBackupLocationInputEl: "#last-backup-location", 60 lastBackupFileNameEl: "#last-backup-filename", 61 lastBackupDateEl: "#last-backup-date", 62 backupLocationShowButtonEl: "#backup-location-show", 63 backupLocationEditButtonEl: "#backup-location-edit", 64 scheduledBackupsDescriptionEl: "#scheduled-backups-description", 65 backupErrorBarEl: "#create-backup-error", 66 backupInProgressMessageBarEl: "#backup-in-progress-message", 67 }; 68 } 69 70 get dialogs() { 71 return [ 72 this.disableBackupEncryptionDialogEl, 73 this.enableBackupEncryptionDialogEl, 74 this.turnOnScheduledBackupsDialogEl, 75 this.turnOffScheduledBackupsDialogEl, 76 this.restoreFromBackupDialogEl, 77 ]; 78 } 79 80 /** 81 * Creates a BackupPreferences instance and sets the initial default 82 * state. 83 */ 84 constructor() { 85 super(); 86 this.backupServiceState = { 87 backupDirPath: "", 88 backupFileToRestore: null, 89 backupFileInfo: null, 90 defaultParent: { 91 fileName: "", 92 path: "", 93 iconURL: "", 94 }, 95 encryptionEnabled: false, 96 scheduledBackupsEnabled: false, 97 lastBackupDate: null, 98 lastBackupFileName: "", 99 supportBaseLink: "", 100 backupInProgress: false, 101 recoveryInProgress: false, 102 recoveryErrorCode: ERRORS.NONE, 103 backupErrorCode: ERRORS.NONE, 104 archiveEnabledStatus: false, 105 restoreEnabledStatus: false, 106 }; 107 this._enableEncryptionTypeAttr = ""; 108 } 109 110 /** 111 * Dispatches the BackupUI:InitWidget custom event upon being attached to the 112 * DOM, which registers with BackupUIChild for BackupService state updates. 113 */ 114 connectedCallback() { 115 super.connectedCallback(); 116 this.dispatchEvent( 117 new CustomEvent("BackupUI:InitWidget", { bubbles: true }) 118 ); 119 120 this.addEventListener("dialogCancel", this); 121 this.addEventListener("restoreFromBackupConfirm", this); 122 this.addEventListener("restoreFromBackupChooseFile", this); 123 } 124 125 handleErrorBarDismiss = () => { 126 // Reset the pref and reactive state; Lit will re-render without the bar. 127 this.dispatchEvent( 128 new CustomEvent("BackupUI:ErrorBarDismissed", { bubbles: true }) 129 ); 130 }; 131 132 handleEvent(event) { 133 switch (event.type) { 134 case "dialogCancel": 135 for (let dialog of this.dialogs) { 136 dialog?.close(); 137 } 138 break; 139 case "restoreFromBackupConfirm": 140 this.dispatchEvent( 141 new CustomEvent("BackupUI:RestoreFromBackupFile", { 142 bubbles: true, 143 composed: true, 144 detail: { 145 backupFile: event.detail.backupFile, 146 backupPassword: event.detail.backupPassword, 147 }, 148 }) 149 ); 150 break; 151 case "restoreFromBackupChooseFile": 152 this.dispatchEvent( 153 new CustomEvent("BackupUI:RestoreFromBackupChooseFile", { 154 bubbles: true, 155 composed: true, 156 }) 157 ); 158 break; 159 } 160 } 161 162 handleBackupTrigger() { 163 this.dispatchEvent( 164 new CustomEvent("BackupUI:TriggerCreateBackup", { 165 bubbles: true, 166 }) 167 ); 168 } 169 170 handleShowScheduledBackups() { 171 if ( 172 !this.backupServiceState.scheduledBackupsEnabled && 173 this.turnOnScheduledBackupsDialogEl 174 ) { 175 this.turnOnScheduledBackupsDialogEl.showModal(); 176 } else if ( 177 this.backupServiceState.scheduledBackupsEnabled && 178 this.turnOffScheduledBackupsDialogEl 179 ) { 180 this.turnOffScheduledBackupsDialogEl.showModal(); 181 } 182 } 183 184 async handleToggleBackupEncryption(event) { 185 event.preventDefault(); 186 187 // Checkbox was unchecked, meaning encryption is already enabled and should be disabled. 188 let toggledToDisable = 189 !event.target.checked && this.backupServiceState.encryptionEnabled; 190 191 if (toggledToDisable && this.disableBackupEncryptionDialogEl) { 192 this.disableBackupEncryptionDialogEl.showModal(); 193 } else { 194 this._enableEncryptionTypeAttr = "set-password"; 195 await this.updateComplete; 196 this.enableBackupEncryptionDialogEl.showModal(); 197 } 198 } 199 200 async handleChangePassword() { 201 if (this.enableBackupEncryptionDialogEl) { 202 this._enableEncryptionTypeAttr = "change-password"; 203 await this.updateComplete; 204 this.enableBackupEncryptionDialogEl.showModal(); 205 } 206 } 207 208 scheduledBackupsDescriptionTemplate() { 209 return html` 210 <div 211 id="scheduled-backups-description" 212 data-l10n-id="settings-data-backup-scheduled-backups-description" 213 > 214 <a 215 is="moz-support-link" 216 support-page="firefox-backup" 217 data-l10n-name="support-link" 218 utm-content="backup-off" 219 ></a> 220 </div> 221 `; 222 } 223 224 turnOnScheduledBackupsDialogTemplate() { 225 let { fileName, path, iconURL } = this.backupServiceState.defaultParent; 226 return html`<dialog 227 id="turn-on-scheduled-backups-dialog" 228 class="backup-dialog" 229 @close=${this.handleTurnOnScheduledBackupsDialogClose} 230 > 231 <turn-on-scheduled-backups 232 defaultlabel=${fileName} 233 defaultpath=${path} 234 defaulticonurl=${iconURL} 235 .supportBaseLink=${this.backupServiceState.supportBaseLink} 236 ></turn-on-scheduled-backups> 237 </dialog>`; 238 } 239 240 turnOffScheduledBackupsDialogTemplate() { 241 return html`<dialog id="turn-off-scheduled-backups-dialog"> 242 <turn-off-scheduled-backups></turn-off-scheduled-backups> 243 </dialog>`; 244 } 245 246 restoreFromBackupDialogTemplate() { 247 return html`<dialog id="restore-from-backup-dialog"> 248 <restore-from-backup></restore-from-backup> 249 </dialog>`; 250 } 251 252 restoreFromBackupTemplate() { 253 let descriptionL10nID = this.backupServiceState.scheduledBackupsEnabled 254 ? "settings-data-backup-scheduled-backups-on-restore-description" 255 : "settings-data-backup-scheduled-backups-off-restore-description"; 256 257 let restoreButtonL10nID = this.backupServiceState.scheduledBackupsEnabled 258 ? "settings-data-backup-scheduled-backups-on-restore-choose" 259 : "settings-data-backup-scheduled-backups-off-restore-choose"; 260 261 return html`<section id="restore-from-backup"> 262 ${this.restoreFromBackupDialogTemplate()} 263 <div class="backups-control"> 264 <span 265 id="restore-header" 266 data-l10n-id="settings-data-backup-restore-header" 267 class="heading-medium" 268 ></span> 269 <moz-button 270 id="backup-toggle-restore-button" 271 @click=${this.handleShowRestoreDialog} 272 data-l10n-id=${restoreButtonL10nID} 273 ></moz-button> 274 <div 275 id="backup-restore-description" 276 data-l10n-id=${descriptionL10nID} 277 ></div> 278 </div> 279 </section>`; 280 } 281 282 handleShowRestoreDialog() { 283 if (this.restoreFromBackupDialogEl) { 284 this.restoreFromBackupDialogEl.showModal(); 285 } 286 } 287 288 handleShowBackupLocation() { 289 this.dispatchEvent( 290 new CustomEvent("BackupUI:ShowBackupLocation", { 291 bubbles: true, 292 }) 293 ); 294 } 295 296 handleEditBackupLocation() { 297 this.dispatchEvent( 298 new CustomEvent("BackupUI:EditBackupLocation", { 299 bubbles: true, 300 }) 301 ); 302 } 303 304 handleTurnOnScheduledBackupsDialogClose() { 305 this.turnOnScheduledBackupsEl.reset(); 306 } 307 308 handleEnableBackupEncryptionDialogClose() { 309 this.enableBackupEncryptionEl.reset(); 310 } 311 312 enableBackupEncryptionDialogTemplate() { 313 return html`<dialog 314 id="enable-backup-encryption-dialog" 315 class="backup-dialog" 316 @close=${this.handleEnableBackupEncryptionDialogClose} 317 > 318 <enable-backup-encryption 319 type=${this._enableEncryptionTypeAttr} 320 .supportBaseLink=${this.backupServiceState.supportBaseLink} 321 ></enable-backup-encryption> 322 </dialog>`; 323 } 324 325 disableBackupEncryptionDialogTemplate() { 326 return html`<dialog id="disable-backup-encryption-dialog"> 327 <disable-backup-encryption></disable-backup-encryption> 328 </dialog>`; 329 } 330 331 lastBackupInfoTemplate() { 332 // The lastBackupDate is stored in preferences, which only accepts 333 // 32-bit signed values, so we automatically divide it by 1000 before 334 // storing it. We need to re-multiply it by 1000 to get Fluent to render 335 // the right time. 336 let backupDateArgs = { 337 date: this.backupServiceState.lastBackupDate * 1000, 338 }; 339 let backupFileNameArgs = { 340 fileName: this.backupServiceState.lastBackupFileName, 341 }; 342 343 return html` 344 <div id="last-backup-info"> 345 <div 346 id="last-backup-date" 347 data-l10n-id="settings-data-backup-last-backup-date" 348 data-l10n-args=${JSON.stringify(backupDateArgs)} 349 ></div> 350 <div 351 id="last-backup-filename" 352 data-l10n-id="settings-data-backup-last-backup-filename" 353 data-l10n-args=${JSON.stringify(backupFileNameArgs)} 354 ></div> 355 </div> 356 `; 357 } 358 359 backupLocationTemplate() { 360 let iconURL = 361 this.backupServiceState.defaultParent.iconURL || this.#placeholderIconURL; 362 let { backupDirPath } = this.backupServiceState; 363 364 return html` 365 <div id="last-backup-location-control"> 366 <span data-l10n-id="settings-data-backup-last-backup-location"></span> 367 <input 368 id="last-backup-location" 369 class="backup-location-filepicker-input" 370 type="text" 371 readonly 372 .value=${backupDirPath} 373 style=${`background-image: url(${iconURL})`}></input> 374 <moz-button 375 id="backup-location-show" 376 @click=${this.handleShowBackupLocation} 377 data-l10n-id="settings-data-backup-last-backup-location-show-in-folder" 378 ></moz-button> 379 <moz-button 380 id="backup-location-edit" 381 @click=${this.handleEditBackupLocation} 382 data-l10n-id="settings-data-backup-last-backup-location-edit" 383 ></moz-button> 384 </div> 385 `; 386 } 387 388 sensitiveDataTemplate() { 389 return html`<section id="backup-password-controls"> 390 <!-- TODO: we can use the moz-checkbox reusable component once it is ready (bug 1901635)--> 391 <div id="backup-sensitive-data-checkbox"> 392 <label 393 id="backup-sensitive-data-checkbox-label" 394 for="backup-sensitive-data-checkbox-input" 395 > 396 <input 397 id="backup-sensitive-data-checkbox-input" 398 @click=${this.handleToggleBackupEncryption} 399 type="checkbox" 400 .checked=${this.backupServiceState.encryptionEnabled} 401 /> 402 <span 403 id="backup-sensitive-data-checkbox-span" 404 data-l10n-id="settings-data-toggle-encryption-label" 405 ></span> 406 </label> 407 <div 408 id="backup-sensitive-data-checkbox-description" 409 class="text-deemphasized" 410 > 411 <span 412 id="backup-sensitive-data-checkbox-description-span" 413 data-l10n-id="settings-sensitive-data-encryption-description" 414 ></span> 415 <a 416 id="settings-data-toggle-encryption-learn-more-link" 417 is="moz-support-link" 418 support-page="firefox-backup" 419 utm-content="encryption" 420 data-l10n-id="settings-data-toggle-encryption-support-link" 421 ></a> 422 </div> 423 </div> 424 ${this.backupServiceState.encryptionEnabled 425 ? html`<moz-button 426 id="backup-change-password-button" 427 @click=${this.handleChangePassword} 428 data-l10n-id="settings-data-change-password" 429 ></moz-button>` 430 : null} 431 </section>`; 432 } 433 434 inProgressMessageBarTemplate() { 435 return html` 436 <moz-message-bar 437 type="info" 438 id="backup-in-progress-message" 439 data-l10n-id="settings-data-backup-in-progress-message" 440 ></moz-message-bar> 441 `; 442 } 443 444 errorBarTemplate() { 445 const l10nId = getErrorL10nId(this.backupServiceState.backupErrorCode); 446 return html` 447 <moz-message-bar 448 type="error" 449 id="create-backup-error" 450 dismissable 451 data-l10n-id=${l10nId} 452 @message-bar:user-dismissed=${this.handleErrorBarDismiss} 453 > 454 <a 455 id="create-backup-error-learn-more-link" 456 slot="support-link" 457 is="moz-support-link" 458 support-page="firefox-backup" 459 data-l10n-id="settings-data-toggle-encryption-support-link" 460 utm-content="backup-error" 461 ></a> 462 </moz-message-bar> 463 `; 464 } 465 466 updated() { 467 if (this.backupServiceState.scheduledBackupsEnabled) { 468 let input = this.lastBackupLocationInputEl; 469 input.setSelectionRange(input.value.length, input.value.length); 470 } 471 } 472 473 render() { 474 let scheduledBackupsEnabledState = 475 this.backupServiceState.scheduledBackupsEnabled; 476 477 let scheduledBackupsEnabledL10nID = scheduledBackupsEnabledState 478 ? "settings-data-backup-scheduled-backups-on" 479 : "settings-data-backup-scheduled-backups-off"; 480 481 let backupToggleL10nID = scheduledBackupsEnabledState 482 ? "settings-data-backup-toggle-off" 483 : "settings-data-backup-toggle-on"; 484 485 if (this.backupServiceState.backupInProgress) { 486 if (!this.showInProgress) { 487 this.showInProgress = true; 488 // Keep the in progress message bar visible for at least 3 seconds 489 clearTimeout(this.inProgressTimeout); 490 this.inProgressTimeout = setTimeout(() => { 491 this.showInProgress = false; 492 this.requestUpdate(); 493 }, this.MESSAGE_BAR_BUFFER); 494 } 495 } 496 497 return html`<link 498 rel="stylesheet" 499 href="chrome://browser/skin/preferences/preferences.css" 500 /> 501 <link 502 rel="stylesheet" 503 href="chrome://browser/content/backup/backup-settings.css" 504 /> 505 ${this.backupServiceState.backupErrorCode 506 ? this.errorBarTemplate() 507 : null} 508 ${this.showInProgress ? this.inProgressMessageBarTemplate() : null} 509 ${this.turnOnScheduledBackupsDialogTemplate()} 510 ${this.turnOffScheduledBackupsDialogTemplate()} 511 ${this.enableBackupEncryptionDialogTemplate()} 512 ${this.disableBackupEncryptionDialogTemplate()} 513 ${this.backupServiceState.archiveEnabledStatus 514 ? html` <section id="scheduled-backups"> 515 <div class="backups-control"> 516 <span 517 id="scheduled-backups-enabled" 518 data-l10n-id=${scheduledBackupsEnabledL10nID} 519 class="heading-medium" 520 ></span> 521 522 ${scheduledBackupsEnabledState 523 ? html` 524 <moz-button 525 id="backup-trigger-button" 526 @click=${this.handleBackupTrigger} 527 data-l10n-id="settings-data-backup-trigger-button" 528 ?disabled=${this.showInProgress} 529 ></moz-button> 530 ` 531 : null} 532 533 <moz-button 534 id="backup-toggle-scheduled-button" 535 @click=${this.handleShowScheduledBackups} 536 data-l10n-id=${backupToggleL10nID} 537 ></moz-button> 538 539 ${this.backupServiceState.scheduledBackupsEnabled 540 ? null 541 : this.scheduledBackupsDescriptionTemplate()} 542 </div> 543 544 ${this.backupServiceState.lastBackupDate 545 ? this.lastBackupInfoTemplate() 546 : null} 547 ${this.backupServiceState.scheduledBackupsEnabled 548 ? this.backupLocationTemplate() 549 : null} 550 ${this.backupServiceState.scheduledBackupsEnabled 551 ? this.sensitiveDataTemplate() 552 : null} 553 </section>` 554 : null} 555 ${this.backupServiceState.restoreEnabledStatus 556 ? this.restoreFromBackupTemplate() 557 : null} `; 558 } 559 } 560 561 customElements.define("backup-settings", BackupSettings);