tor-browser

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

securityLevelDialog.js (7624B)


      1 "use strict";
      2 
      3 const { SecurityLevelPrefs } = ChromeUtils.importESModule(
      4  "resource://gre/modules/SecurityLevel.sys.mjs"
      5 );
      6 const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
      7  "resource:///modules/SecurityLevelUIUtils.sys.mjs"
      8 );
      9 
     10 const gSecurityLevelDialog = {
     11  /**
     12   * The security level when this dialog was opened.
     13   *
     14   * @type {string}
     15   */
     16  _prevLevel: SecurityLevelPrefs.securityLevelSummary,
     17  /**
     18   * The security level currently selected.
     19   *
     20   * @type {string}
     21   */
     22  _selectedLevel: "",
     23  /**
     24   * The radiogroup for this preference.
     25   *
     26   * @type {?Element}
     27   */
     28  _radiogroup: null,
     29  /**
     30   * A list of radio options and their containers.
     31   *
     32   * @type {?Array<{ container: Element, radio: Element }>}
     33   */
     34  _radioOptions: null,
     35 
     36  /**
     37   * Initialise the dialog.
     38   */
     39  async init() {
     40    const dialog = document.getElementById("security-level-dialog");
     41    dialog.addEventListener("dialogaccept", event => {
     42      event.preventDefault();
     43      if (this._acceptButton.disabled) {
     44        return;
     45      }
     46      this._commitChange();
     47    });
     48 
     49    this._acceptButton = dialog.getButton("accept");
     50 
     51    document.l10n.setAttributes(
     52      this._acceptButton,
     53      "security-level-dialog-save-restart"
     54    );
     55 
     56    this._radiogroup = document.getElementById("security-level-radiogroup");
     57 
     58    this._radioOptions = Array.from(
     59      this._radiogroup.querySelectorAll(".security-level-radio-container"),
     60      container => {
     61        return {
     62          container,
     63          radio: container.querySelector(".security-level-radio"),
     64        };
     65      }
     66    );
     67 
     68    for (const { container, radio } of this._radioOptions) {
     69      const level = radio.value;
     70      radio.id = `security-level-radio-${level}`;
     71      const currentEl = container.querySelector(
     72        ".security-level-current-badge"
     73      );
     74      currentEl.id = `security-level-current-badge-${level}`;
     75      const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
     76        level,
     77        document
     78      );
     79      descriptionEl.classList.add("indent");
     80      descriptionEl.id = `security-level-description-${level}`;
     81 
     82      // Wait for the full translation of the element before adding it to the
     83      // DOM. In particular, we want to make sure the elements have text before
     84      // we measure the maxHeight below.
     85      await document.l10n.translateFragment(descriptionEl);
     86      document.l10n.pauseObserving();
     87      container.append(descriptionEl);
     88      document.l10n.resumeObserving();
     89 
     90      if (level === this._prevLevel) {
     91        currentEl.hidden = false;
     92        // When the currentEl is visible, include it in the accessible name for
     93        // the radio option.
     94        // NOTE: The currentEl has an accessible name which includes punctuation
     95        // to help separate it's content from the security level name.
     96        // E.g. "Standard (Current level)".
     97        radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
     98      } else {
     99        currentEl.hidden = true;
    100      }
    101      // We point the accessible description to the wrapping
    102      // .security-level-description element, rather than its children
    103      // that define the actual text content. This means that when the
    104      // privacy-extra-information is shown or hidden, its text content is
    105      // included or excluded from the accessible description, respectively.
    106      radio.setAttribute("aria-describedby", descriptionEl.id);
    107    }
    108 
    109    // We want to reserve the maximum height of the radiogroup so that the
    110    // dialog has enough height when the user switches options. So we cycle
    111    // through the options and measure the height when they are selected to set
    112    // a minimum height that fits all of them.
    113    // NOTE: At the time of implementation, at this point the dialog may not
    114    // yet have the "subdialog" attribute, which means it is missing the
    115    // common.css stylesheet from its shadow root, which effects the size of the
    116    // .radio-check element and the font. Therefore, we have duplicated the
    117    // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
    118    // at this earlier stage.
    119    let maxHeight = 0;
    120    for (const { container } of this._radioOptions) {
    121      container.classList.add("selected");
    122      maxHeight = Math.max(
    123        maxHeight,
    124        this._radiogroup.getBoundingClientRect().height
    125      );
    126      container.classList.remove("selected");
    127    }
    128    this._radiogroup.style.minHeight = `${maxHeight}px`;
    129 
    130    if (this._prevLevel !== "custom") {
    131      this._selectedLevel = this._prevLevel;
    132      this._radiogroup.value = this._prevLevel;
    133    } else {
    134      this._radiogroup.selectedItem = null;
    135    }
    136 
    137    this._radiogroup.addEventListener("select", () => {
    138      this._selectedLevel = this._radiogroup.value;
    139      this._updateSelected();
    140    });
    141 
    142    this._updateSelected();
    143  },
    144 
    145  /**
    146   * Update the UI in response to a change in selection.
    147   */
    148  _updateSelected() {
    149    this._acceptButton.disabled =
    150      !this._selectedLevel || this._selectedLevel === this._prevLevel;
    151    // Have the container's `selected` CSS class match the selection state of
    152    // the radio elements.
    153    for (const { container, radio } of this._radioOptions) {
    154      container.classList.toggle("selected", radio.selected);
    155    }
    156  },
    157 
    158  /**
    159   * Commit the change in security level and restart the browser.
    160   */
    161  async _commitChange() {
    162    const doNotWarnPref = "browser.security_level.disable_warn_before_restart";
    163    if (!Services.prefs.getBoolPref(doNotWarnPref, false)) {
    164      const [titleString, bodyString, checkboxString, restartString] =
    165        await document.l10n.formatValues([
    166          { id: "security-level-restart-warning-dialog-title" },
    167          { id: "security-level-restart-warning-dialog-body" },
    168          { id: "restart-warning-dialog-do-not-warn-checkbox" },
    169          { id: "restart-warning-dialog-restart-button" },
    170        ]);
    171      const flags =
    172        Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
    173        Services.prompt.BUTTON_POS_0_DEFAULT +
    174        Services.prompt.BUTTON_DEFAULT_IS_DESTRUCTIVE +
    175        Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
    176      const propBag = await Services.prompt.asyncConfirmEx(
    177        window.browsingContext.top,
    178        Services.prompt.MODAL_TYPE_CONTENT,
    179        titleString,
    180        bodyString,
    181        flags,
    182        restartString,
    183        null,
    184        null,
    185        checkboxString,
    186        false,
    187        { useTitle: true, noIcon: true }
    188      );
    189      if (propBag.get("buttonNumClicked") !== 0) {
    190        return;
    191      }
    192      if (propBag.get("checked")) {
    193        Services.prefs.setBoolPref(doNotWarnPref, true);
    194      }
    195    }
    196    SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
    197    Services.startup.quit(
    198      Services.startup.eAttemptQuit | Services.startup.eRestart
    199    );
    200  },
    201 };
    202 
    203 // Initial focus is not visible, even if opened with a keyboard. We avoid the
    204 // default handler and manage the focus ourselves, which will paint the focus
    205 // ring by default.
    206 // NOTE: A side effect is that the focus ring will show even if the user opened
    207 // with a mouse event.
    208 // TODO: Remove this once bugzilla bug 1708261 is resolved.
    209 document.subDialogSetDefaultFocus = () => {
    210  document.getElementById("security-level-radiogroup").focus();
    211 };
    212 
    213 // Delay showing and sizing the subdialog until it is fully initialised.
    214 document.mozSubdialogReady = new Promise(resolve => {
    215  window.addEventListener(
    216    "DOMContentLoaded",
    217    () => {
    218      gSecurityLevelDialog.init().finally(resolve);
    219    },
    220    { once: true }
    221  );
    222 });