tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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);