tor-browser

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

turn-on-scheduled-backups.mjs (15710B)


      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, nothing } from "chrome://global/content/vendor/lit.all.mjs";
      6 import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
      7 
      8 // eslint-disable-next-line import/no-unassigned-import
      9 import "chrome://global/content/elements/moz-message-bar.mjs";
     10 // eslint-disable-next-line import/no-unassigned-import
     11 import "chrome://browser/content/backup/password-validation-inputs.mjs";
     12 
     13 import { ERRORS } from "chrome://browser/content/backup/backup-constants.mjs";
     14 
     15 const ENABLE_ERROR_L10N_IDS = Object.freeze({
     16  [ERRORS.FILE_SYSTEM_ERROR]: "turn-on-scheduled-backups-error-file-system",
     17  [ERRORS.INVALID_PASSWORD]: "backup-error-password-requirements",
     18  [ERRORS.UNKNOWN]: "backup-error-retry",
     19 });
     20 
     21 /**
     22 * @param {number} errorCode Error code from backup-constants.mjs
     23 * @returns {string} Localization ID for error message
     24 */
     25 function getEnableErrorL10nId(errorCode) {
     26  return (
     27    ENABLE_ERROR_L10N_IDS[errorCode] ?? ENABLE_ERROR_L10N_IDS[ERRORS.UNKNOWN]
     28  );
     29 }
     30 
     31 /**
     32 * The widget for showing available options when users want to turn on
     33 * scheduled backups.
     34 */
     35 export default class TurnOnScheduledBackups extends MozLitElement {
     36  #placeholderIconURL = "chrome://global/skin/icons/page-portrait.svg";
     37 
     38  static properties = {
     39    backupServiceState: { type: Object },
     40    // passed in from parents
     41    defaultIconURL: { type: String, reflect: true },
     42    defaultLabel: { type: String, reflect: true },
     43    defaultPath: { type: String, reflect: true },
     44    supportBaseLink: { type: String },
     45    embeddedFxBackupOptIn: {
     46      type: Boolean,
     47      reflect: true,
     48      attribute: "embedded-fx-backup-opt-in",
     49    },
     50    hideFilePathChooser: {
     51      type: Boolean,
     52      reflect: true,
     53      attribute: "hide-file-path-chooser",
     54    },
     55    hideSecondaryButton: {
     56      type: Boolean,
     57      reflect: true,
     58      attribute: "hide-secondary-button",
     59    },
     60    backupIsEncrypted: {
     61      type: Boolean,
     62      reflect: true,
     63      attribute: "backup-is-encrypted",
     64    },
     65    filePathLabelL10nId: {
     66      type: String,
     67      reflect: true,
     68      attribute: "file-path-label-l10n-id",
     69    },
     70    turnOnBackupHeaderL10nId: {
     71      type: String,
     72      reflect: true,
     73      attribute: "turn-on-backup-header-l10n-id",
     74    },
     75    createPasswordLabelL10nId: {
     76      type: String,
     77      reflect: true,
     78      attribute: "create-password-label-l10n-id",
     79    },
     80    turnOnBackupConfirmBtnL10nId: {
     81      type: String,
     82      reflect: true,
     83      attribute: "turn-on-backup-confirm-btn-l10n-id",
     84    },
     85    turnOnBackupCancelBtnL10nId: {
     86      type: String,
     87      reflect: true,
     88      attribute: "turn-on-backup-cancel-btn-l10n-id",
     89    },
     90 
     91    // internal state
     92    _newIconURL: { type: String, state: true },
     93    _newLabel: { type: String, state: true },
     94    _newPath: { type: String, state: true },
     95    _showPasswordOptions: { type: Boolean, reflect: true, state: true },
     96    _passwordsMatch: { type: Boolean, state: true },
     97    _inputPassValue: { type: String, state: true },
     98 
     99    // managed by BackupUIChild
    100    enableBackupErrorCode: { type: Number },
    101  };
    102 
    103  static get queries() {
    104    return {
    105      cancelButtonEl: "#backup-turn-on-scheduled-cancel-button",
    106      confirmButtonEl: "#backup-turn-on-scheduled-confirm-button",
    107      filePathButtonEl: "#backup-location-filepicker-button",
    108      filePathInputCustomEl: "#backup-location-filepicker-input-custom",
    109      filePathInputDefaultEl: "#backup-location-filepicker-input-default",
    110      passwordOptionsCheckboxEl: "#sensitive-data-checkbox-input",
    111      passwordOptionsExpandedEl: "#passwords",
    112      errorEl: "#enable-backup-encryption-error",
    113    };
    114  }
    115 
    116  constructor() {
    117    super();
    118    this.backupServiceState = {};
    119    this.defaultIconURL = "";
    120    this.defaultLabel = "";
    121    this.defaultPath = "";
    122    this._newIconURL = "";
    123    this._newLabel = "";
    124    this._newPath = "";
    125    this._showPasswordOptions = false;
    126    this._passwordsMatch = false;
    127    this.enableBackupErrorCode = 0;
    128    this.disableSubmit = false;
    129  }
    130 
    131  connectedCallback() {
    132    super.connectedCallback();
    133    this.dispatchEvent(
    134      new CustomEvent("BackupUI:InitWidget", { bubbles: true })
    135    );
    136 
    137    // listen to events from BackupUIChild
    138    this.addEventListener("BackupUI:SelectNewFilepickerPath", this);
    139 
    140    // listen to events from <password-validation-inputs>
    141    this.addEventListener("ValidPasswordsDetected", this);
    142    this.addEventListener("InvalidPasswordsDetected", this);
    143 
    144    // listens to keydown events
    145    this.addEventListener("keydown", this);
    146  }
    147 
    148  handleEvent(event) {
    149    if (event.type == "BackupUI:SelectNewFilepickerPath") {
    150      let { path, filename, iconURL } = event.detail;
    151      this._newPath = path;
    152      this._newLabel = filename;
    153      this._newIconURL = iconURL;
    154 
    155      if (this.embeddedFxBackupOptIn) {
    156        // Let's set a persistent path
    157        this.dispatchEvent(
    158          new CustomEvent("BackupUI:SetEmbeddedComponentPersistentData", {
    159            bubbles: true,
    160            detail: {
    161              path,
    162              label: filename,
    163              iconURL,
    164            },
    165          })
    166        );
    167      }
    168    } else if (event.type == "ValidPasswordsDetected") {
    169      let { password } = event.detail;
    170      this._passwordsMatch = true;
    171      this._inputPassValue = password;
    172    } else if (event.type == "InvalidPasswordsDetected") {
    173      this._passwordsMatch = false;
    174      this._inputPassValue = "";
    175    } else if (event.type == "keydown") {
    176      if (
    177        event.key === "Enter" &&
    178        (event.originalTarget.id ==
    179          "backup-location-filepicker-input-default" ||
    180          event.originalTarget.id == "backup-location-filepicker-input-custom")
    181      ) {
    182        event.preventDefault();
    183      }
    184    }
    185  }
    186 
    187  async handleChooseLocation() {
    188    this.dispatchEvent(
    189      new CustomEvent("BackupUI:ShowFilepicker", {
    190        bubbles: true,
    191        detail: {
    192          win: window.browsingContext,
    193        },
    194      })
    195    );
    196  }
    197 
    198  close() {
    199    this.dispatchEvent(
    200      new CustomEvent("dialogCancel", {
    201        bubbles: true,
    202        composed: true,
    203      })
    204    );
    205  }
    206 
    207  handleConfirm() {
    208    let detail = {
    209      parentDirPath: this._newPath || this.defaultPath,
    210    };
    211 
    212    if (this._showPasswordOptions && this._passwordsMatch) {
    213      detail.password = this._inputPassValue;
    214    }
    215 
    216    if (this.embeddedFxBackupOptIn && this.backupIsEncrypted) {
    217      if (!detail.password) {
    218        // We're in the embedded component and we haven't set a password yet
    219        // when one is expected, let's not do a confirm action yet!
    220        this.dispatchEvent(
    221          new CustomEvent("SpotlightOnboardingAdvanceScreens", {
    222            bubbles: true,
    223          })
    224        );
    225        return;
    226      }
    227 
    228      // The persistent data will take precedence over the default path
    229      detail.parentDirPath =
    230        this.backupServiceState?.embeddedComponentPersistentData?.path ||
    231        detail.parentDirPath;
    232    }
    233 
    234    this.dispatchEvent(
    235      new CustomEvent("BackupUI:EnableScheduledBackups", {
    236        bubbles: true,
    237        detail,
    238      })
    239    );
    240  }
    241 
    242  handleTogglePasswordOptions() {
    243    this._showPasswordOptions = this.passwordOptionsCheckboxEl?.checked;
    244    this._passwordsMatch = false;
    245  }
    246 
    247  updated(changedProperties) {
    248    super.updated?.(changedProperties);
    249 
    250    if (changedProperties.has("hideFilePathChooser")) {
    251      // If hideFilePathChooser is true, show password options
    252      this._showPasswordOptions = !!this.hideFilePathChooser;
    253 
    254      // Uncheck the checkbox if it exists
    255      if (this.passwordOptionsCheckboxEl) {
    256        this.passwordOptionsCheckboxEl.checked = this._showPasswordOptions;
    257      }
    258    }
    259  }
    260 
    261  reset() {
    262    this._showPasswordOptions = false;
    263    this.passwordOptionsCheckboxEl.checked = false;
    264    this._passwordsMatch = false;
    265    this._inputPassValue = "";
    266    this.enableBackupErrorCode = 0;
    267    this.disableSubmit = false;
    268    // we don't want to reset the path when embedded in the spotlight
    269    if (!this.embeddedFxBackupOptIn) {
    270      this._newPath = "";
    271      this._newIconURL = "";
    272      this._newLabel = "";
    273    }
    274 
    275    if (this.passwordOptionsExpandedEl) {
    276      /** @type {import("./password-validation-inputs.mjs").default} */
    277      const passwordElement = this.passwordOptionsExpandedEl;
    278      passwordElement.reset();
    279    }
    280 
    281    if (
    282      this.embeddedFxBackupOptIn &&
    283      this.backupServiceState?.embeddedComponentPersistentData
    284    ) {
    285      this.dispatchEvent(
    286        new CustomEvent("BackupUI:FlushEmbeddedComponentPersistentData", {
    287          bubbles: true,
    288        })
    289      );
    290    }
    291  }
    292 
    293  defaultFilePathInputTemplate() {
    294    let filename = this.defaultLabel;
    295    let iconURL = this.defaultIconURL || this.#placeholderIconURL;
    296 
    297    const hasFilename = !!filename;
    298    const l10nArgs = hasFilename
    299      ? JSON.stringify({ recommendedFolder: filename })
    300      : null;
    301 
    302    return html`
    303      <input
    304        id="backup-location-filepicker-input-default"
    305        class="backup-location-filepicker-input"
    306        type="text"
    307        readonly
    308        data-l10n-id=${hasFilename
    309          ? "turn-on-scheduled-backups-location-default-folder"
    310          : nothing}
    311        data-l10n-args=${hasFilename ? l10nArgs : nothing}
    312        data-l10n-attrs=${hasFilename ? "value" : nothing}
    313        style=${`background-image: url(${iconURL})`}
    314      />
    315    `;
    316  }
    317 
    318  /**
    319   * Note: We also consider the embeddedComponentPersistentData since we might be in the
    320   *    Spotlight where we need this persistent data between screens. This state property should
    321   *    not be set if we are not in the Spotlight.
    322   */
    323  customFilePathInputTemplate() {
    324    let filename =
    325      this._newLabel ||
    326      this.backupServiceState?.embeddedComponentPersistentData?.label;
    327    let iconURL =
    328      this._newIconURL ||
    329      this.backupServiceState?.embeddedComponentPersistentData?.iconURL ||
    330      this.#placeholderIconURL;
    331 
    332    return html`
    333      <input
    334        id="backup-location-filepicker-input-custom"
    335        class="backup-location-filepicker-input"
    336        type="text"
    337        readonly
    338        .value=${filename}
    339        style=${`background-image: url(${iconURL})`}
    340      />
    341    `;
    342  }
    343 
    344  errorTemplate() {
    345    return html`
    346      <moz-message-bar
    347        id="enable-backup-encryption-error"
    348        type="error"
    349        .messageL10nId=${getEnableErrorL10nId(this.enableBackupErrorCode)}
    350      ></moz-message-bar>
    351    `;
    352  }
    353 
    354  allOptionsTemplate() {
    355    return html`
    356      <fieldset id="all-controls">
    357        <div id="backup-location-controls">
    358          <label
    359            id="backup-location-label"
    360            for="backup-location-filepicker-input"
    361            data-l10n-id=${this.filePathLabelL10nId ||
    362            "turn-on-scheduled-backups-location-label"}
    363          ></label>
    364          <div id="backup-location-filepicker">
    365            ${!this._newPath &&
    366            !this.backupServiceState?.embeddedComponentPersistentData?.path
    367              ? this.defaultFilePathInputTemplate()
    368              : this.customFilePathInputTemplate()}
    369            <moz-button
    370              id="backup-location-filepicker-button"
    371              @click=${this.handleChooseLocation}
    372              data-l10n-id="turn-on-scheduled-backups-location-choose-button"
    373              aria-controls="backup-location-filepicker-input"
    374            ></moz-button>
    375          </div>
    376        </div>
    377        <fieldset id="sensitive-data-controls">
    378          <div id="sensitive-data-checkbox">
    379            <label
    380              id="sensitive-data-checkbox-label"
    381              for="sensitive-data-checkbox-input"
    382              aria-controls="passwords"
    383              aria-expanded=${this._showPasswordOptions}
    384            >
    385              <input
    386                id="sensitive-data-checkbox-input"
    387                .value=${this._showPasswordOptions}
    388                @click=${this.handleTogglePasswordOptions}
    389                type="checkbox"
    390              />
    391              <span
    392                id="sensitive-data-checkbox-span"
    393                data-l10n-id="turn-on-scheduled-backups-encryption-label"
    394              ></span>
    395            </label>
    396            <span
    397              class="text-deemphasized"
    398              data-l10n-id="settings-sensitive-data-encryption-description"
    399            ></span>
    400          </div>
    401 
    402          ${this._showPasswordOptions ? this.passwordsTemplate() : null}
    403        </fieldset>
    404      </fieldset>
    405    `;
    406  }
    407 
    408  passwordsTemplate() {
    409    return html`
    410      <password-validation-inputs
    411        id="passwords"
    412        .supportBaseLink=${this.supportBaseLink}
    413        .createPasswordLabelL10nId=${this.createPasswordLabelL10nId}
    414        ?embedded-fx-backup-opt-in=${this.embeddedFxBackupOptIn}
    415      ></password-validation-inputs>
    416    `;
    417  }
    418 
    419  contentTemplate() {
    420    const hasEmbeddedPersistentData =
    421      this.embeddedFxBackupOptIn &&
    422      this.backupServiceState?.embeddedComponentPersistentData?.path;
    423    // All the situations where we want to disable submit:
    424    // - passwords don't match
    425    // - there's no destination folder
    426    // - other unknown errors
    427    if (
    428      (this._showPasswordOptions && !this._passwordsMatch) ||
    429      (!this._newPath && !this.defaultLabel && !hasEmbeddedPersistentData) ||
    430      this.enableBackupErrorCode != ERRORS.NONE
    431    ) {
    432      this.disableSubmit = true;
    433    } else {
    434      this.disableSubmit = false;
    435    }
    436 
    437    return html`
    438      <form
    439        id="backup-turn-on-scheduled-wrapper"
    440        aria-labelledby="backup-turn-on-scheduled-header"
    441        aria-describedby="backup-turn-on-scheduled-description"
    442        part="form"
    443      >
    444        <h1
    445          id="backup-turn-on-scheduled-header"
    446          class="heading-medium"
    447          data-l10n-id=${this.turnOnBackupHeaderL10nId ||
    448          "turn-on-scheduled-backups-header"}
    449        ></h1>
    450        <main id="backup-turn-on-scheduled-content">
    451          <div id="backup-turn-on-scheduled-description">
    452            <span
    453              id="backup-turn-on-scheduled-description-span"
    454              data-l10n-id="turn-on-scheduled-backups-description"
    455            ></span>
    456            <a
    457              id="backup-turn-on-scheduled-learn-more-link"
    458              is="moz-support-link"
    459              support-page="firefox-backup"
    460              data-l10n-id="turn-on-scheduled-backups-support-link"
    461              utm-content="turn-on-backup"
    462            ></a>
    463          </div>
    464          ${this.allOptionsTemplate()}
    465          ${this.enableBackupErrorCode ? this.errorTemplate() : null}
    466        </main>
    467 
    468        <moz-button-group id="backup-turn-on-scheduled-button-group">
    469          <moz-button
    470            id="backup-turn-on-scheduled-cancel-button"
    471            @click=${this.close}
    472            data-l10n-id=${this.turnOnBackupCancelBtnL10nId ||
    473            "turn-on-scheduled-backups-cancel-button"}
    474          ></moz-button>
    475          <moz-button
    476            id="backup-turn-on-scheduled-confirm-button"
    477            form="backup-turn-on-scheduled-wrapper"
    478            @click=${this.handleConfirm}
    479            type="primary"
    480            data-l10n-id=${this.turnOnBackupConfirmBtnL10nId ||
    481            "turn-on-scheduled-backups-confirm-button"}
    482            ?disabled=${this.disableSubmit}
    483          ></moz-button>
    484        </moz-button-group>
    485      </form>
    486    `;
    487  }
    488 
    489  render() {
    490    return html`
    491      <link
    492        rel="stylesheet"
    493        href="chrome://browser/content/backup/turn-on-scheduled-backups.css"
    494      />
    495      ${this.contentTemplate()}
    496    `;
    497  }
    498 }
    499 
    500 customElements.define("turn-on-scheduled-backups", TurnOnScheduledBackups);