tor-browser

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

aboutTor.js (15230B)


      1 "use strict";
      2 
      3 const SearchWidget = {
      4  _initialized: false,
      5  _initialOnionize: false,
      6 
      7  /**
      8   * Initialize the search form elements.
      9   */
     10  init() {
     11    this._initialized = true;
     12 
     13    this.searchForm = document.getElementById("search-form");
     14    this.onionizeToggle = document.getElementById("onionize-toggle");
     15    this.onionizeToggle.pressed = this._initialOnionize;
     16    this._updateOnionize();
     17    this.onionizeToggle.addEventListener("toggle", () =>
     18      this._updateOnionize()
     19    );
     20 
     21    // If the user submits, save the onionize search state for the next about:tor
     22    // page.
     23    this.searchForm.addEventListener("submit", () => {
     24      dispatchEvent(
     25        new CustomEvent("SubmitSearchOnionize", {
     26          detail: this.onionizeToggle.pressed,
     27          bubbles: true,
     28        })
     29      );
     30    });
     31 
     32    // By default, Enter on the onionizeToggle will toggle the button rather
     33    // than submit the <form>.
     34    // Moreover, our <form> has no submit button, so can only be submitted by
     35    // pressing Enter.
     36    // For keyboard users, Space will also toggle the form. We do not want to
     37    // require users to have to Tab back to the search input in order to press
     38    // Enter to submit the form.
     39    // For mouse users, clicking the toggle button will give it focus, so they
     40    // would have to Tab back or click the search input in order to submit the
     41    // form.
     42    // So we want to intercept the Enter keydown event to submit the form.
     43    this.onionizeToggle.addEventListener(
     44      "keydown",
     45      event => {
     46        if (event.key !== "Enter") {
     47          return;
     48        }
     49        event.preventDefault();
     50        event.stopPropagation();
     51        this.searchForm.requestSubmit();
     52      },
     53      { capture: true }
     54    );
     55  },
     56 
     57  _updateOnionize() {
     58    // Change submit URL based on the onionize toggle.
     59    this.searchForm.action = this.onionizeToggle.pressed
     60      ? "https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion"
     61      : "https://duckduckgo.com";
     62    this.searchForm.classList.toggle(
     63      "onionized-search",
     64      this.onionizeToggle.pressed
     65    );
     66  },
     67 
     68  /**
     69   * Set what the "Onionize" toggle state.
     70   *
     71   * @param {boolean} state - Whether the "Onionize" toggle should be switched
     72   *   on.
     73   */
     74  setOnionizeState(state) {
     75    if (!this._initialized) {
     76      this._initialOnionize = state;
     77      return;
     78    }
     79    this.onionizeToggle.pressed = state;
     80    this._updateOnionize();
     81  },
     82 };
     83 
     84 const MessageArea = {
     85  _initialized: false,
     86  _messageData: null,
     87  _isStable: null,
     88  _torConnectEnabled: null,
     89 
     90  /**
     91   * Initialize the message area and heading once elements are available.
     92   */
     93  init() {
     94    this._initialized = true;
     95    this._update();
     96  },
     97 
     98  /**
     99   * Set the message data and stable release flag.
    100   *
    101   * @param {MessageData} messageData - The message data, indicating which
    102   *   message to show.
    103   * @param {boolean} isStable - Whether this is the stable release version.
    104   * @param {boolean} torConnectEnabled - Whether TorConnect is enabled, and
    105   *   therefore the Tor process was configured with about:torconnect.
    106   */
    107  setMessageData(messageData, isStable, torConnectEnabled) {
    108    this._messageData = messageData;
    109    this._isStable = isStable;
    110    this._torConnectEnabled = torConnectEnabled;
    111    this._update();
    112  },
    113 
    114  _update() {
    115    if (!this._initialized) {
    116      return;
    117    }
    118 
    119    document
    120      .querySelector(".home-message.shown-message")
    121      ?.classList.remove("shown-message");
    122 
    123    if (!this._messageData) {
    124      return;
    125    }
    126 
    127    // Set heading.
    128    document.body.classList.toggle("is-testing", !this._isStable);
    129 
    130    document.body.classList.toggle("show-tor-check", !this._torConnectEnabled);
    131 
    132    const { updateVersion, updateURL, number } = this._messageData;
    133 
    134    if (updateVersion) {
    135      const updatedElement = document.getElementById("home-message-updated");
    136      updatedElement.querySelector("a").href = updateURL;
    137      document.l10n.setAttributes(
    138        updatedElement.querySelector("span"),
    139        "tor-browser-home-message-updated",
    140        { version: updateVersion }
    141      );
    142      updatedElement.classList.add("shown-message");
    143    } else {
    144      const messageElements = document.querySelectorAll(
    145        this._isStable
    146          ? ".home-message-rotating-stable"
    147          : ".home-message-rotating-testing"
    148      );
    149      messageElements[number % messageElements.length].classList.add(
    150        "shown-message"
    151      );
    152    }
    153  },
    154 };
    155 
    156 /**
    157 * A reusable area for surveys.
    158 *
    159 * Initially used for tor-browser#43504.
    160 */
    161 const SurveyArea = {
    162  /**
    163   * The current version of the survey.
    164   *
    165   * Should be increased every time we start a new survey campaign.
    166   *
    167   * @type {integer}
    168   */
    169  _version: 1,
    170 
    171  /**
    172   * The date to start showing the survey.
    173   *
    174   * @type {?integer}
    175   */
    176  _startDate: null, // No survey date.
    177 
    178  /**
    179   * The date to stop showing the current survey.
    180   *
    181   * @type {?integer}
    182   */
    183  _endDate: null, // No survey date.
    184 
    185  /**
    186   * The survey URL.
    187   *
    188   * @type {string}
    189   */
    190  _urlBase: "https://survey.torproject.org/index.php/923269",
    191 
    192  /**
    193   * @typedef {object} SurveyLocaleData
    194   *
    195   * Locale-specific data for the survey.
    196   *
    197   * @property {string[]} browserLocales - The browser locales this should match
    198   *   with. The first locale should match the locale of the strings.
    199   * @property {string} urlCode - The language code to pass to the survey URL.
    200   * @property {string} dir - The direction of the locale.
    201   * @property {object} strings - The strings to use for the survey banner.
    202   */
    203 
    204  /**
    205   * The data for the selected locale.
    206   *
    207   * @type {SurveyLocaleData}
    208   */
    209  _localeData: null,
    210 
    211  /**
    212   * The data for each locale that is supported.
    213   *
    214   * The first entry is the default.
    215   *
    216   * @type {SurveyLocaleData[]}
    217   */
    218  _localeDataSet: [
    219    {
    220      browserLocales: ["en-US"],
    221      dir: "ltr",
    222      urlCode: "en",
    223      strings: {
    224        heading: "We’d love your feedback",
    225        body: "Help us improve Tor Browser by completing this 10-minute survey.",
    226        launch: "Launch the survey",
    227        dismiss: "Dismiss",
    228        close: "Close",
    229      },
    230    },
    231    {
    232      browserLocales: ["es-ES"],
    233      dir: "ltr",
    234      urlCode: "es",
    235      strings: {
    236        heading: "Danos tu opinión",
    237        body: "Ayúdanos a mejorar el Navegador Tor completando esta encuesta de 10 minutos.",
    238        launch: "Iniciar la encuesta",
    239        dismiss: "Descartar",
    240        close: "Cerrar",
    241      },
    242    },
    243    {
    244      browserLocales: ["ru"],
    245      dir: "ltr",
    246      urlCode: "ru",
    247      strings: {
    248        heading: "Мы будем рады вашим отзывам",
    249        body: "Помогите нам улучшить браузер Tor, пройдя 10-минутный опрос.",
    250        launch: "Начать опрос",
    251        dismiss: "Отклонить",
    252        close: "Закрыть",
    253      },
    254    },
    255    {
    256      browserLocales: ["fr"],
    257      dir: "ltr",
    258      urlCode: "fr",
    259      strings: {
    260        heading: "Nous serions ravis d’avoir votre avis !",
    261        body: "Aidez-nous à améliorer le navigateur Tor en répondant à cette enquête de 10 minutes.",
    262        launch: "Lancer l'enquête",
    263        dismiss: "Ignorer",
    264        close: "Fermer",
    265      },
    266    },
    267    {
    268      // Also show this pt-BR banner for the pt-PT browser locale.
    269      browserLocales: ["pt-BR", "pt-PT"],
    270      dir: "ltr",
    271      urlCode: "pt-BR",
    272      strings: {
    273        heading: "Adoraríamos ouvir sua opinião",
    274        body: "Ajude-nos a melhorar o Navegador Tor respondendo a esta pesquisa de 10 minutos.",
    275        launch: "Iniciar a pesquisa",
    276        dismiss: "Dispensar",
    277        close: "Fechar",
    278      },
    279    },
    280  ],
    281 
    282  /**
    283   * Whether the area has been initialised.
    284   *
    285   * @type {boolean}
    286   */
    287  _initialized: false,
    288 
    289  /**
    290   * The app locale, or `null` whilst unset.
    291   *
    292   * @type {?string}
    293   */
    294  _locale: null,
    295 
    296  /**
    297   * Whether the banner should be shown.
    298   *
    299   * @type {boolean}
    300   */
    301  _shouldShow: false,
    302 
    303  /**
    304   * The survey element.
    305   *
    306   * @type {?Element}
    307   */
    308  _areaEl: null,
    309 
    310  /**
    311   * Initialize the survey area.
    312   */
    313  init() {
    314    this._initialized = true;
    315 
    316    this._areaEl = document.getElementById("survey");
    317    document.getElementById("survey-launch").addEventListener("click", () => {
    318      const url = URL.parse(this._urlBase);
    319      if (!url || !this._localeData) {
    320        return;
    321      }
    322 
    323      url.searchParams.append("lang", this._localeData.urlCode);
    324      open(url.href);
    325    });
    326    document.getElementById("survey-close").addEventListener("click", () => {
    327      this._hide();
    328    });
    329    document.getElementById("survey-dismiss").addEventListener("click", () => {
    330      this._hide();
    331    });
    332 
    333    this._update();
    334  },
    335 
    336  /**
    337   * Permanently hide this survey.
    338   */
    339  _hide() {
    340    this._shouldShow = false;
    341    this._update();
    342 
    343    dispatchEvent(
    344      new CustomEvent("SurveyDismissed", {
    345        // We pass in the current survey version to record the *latest*
    346        // version that the user has dismissed. This will overwrite any
    347        // previous versions.
    348        detail: this._version,
    349        bubbles: true,
    350      })
    351    );
    352  },
    353 
    354  /**
    355   * Decide whether to show the survey.
    356   *
    357   * @param {integer} dismissVersion - The latest version of survey that the
    358   *   user has already dismissed.
    359   * @param {boolean} isStable - Whether this is the stable release of Tor
    360   *   Browser.
    361   * @param {string} appLocale - The app locale currently in use.
    362   */
    363  potentiallyShow(dismissVersion, isStable, appLocale) {
    364    const now = Date.now();
    365    this._shouldShow =
    366      isStable &&
    367      dismissVersion < this._version &&
    368      this._startDate &&
    369      now >= this._startDate &&
    370      now < this._endDate;
    371    this._locale = appLocale;
    372    this._update();
    373  },
    374 
    375  /**
    376   * Update the display.
    377   */
    378  _update() {
    379    if (!this._initialized) {
    380      return;
    381    }
    382    if (!this._shouldShow) {
    383      if (this._areaEl.contains(document.activeElement)) {
    384        // Move focus to the search input.
    385        document.getElementById("search-input").focus();
    386      }
    387      document.body.classList.remove("show-survey");
    388      return;
    389    }
    390 
    391    // Determine the survey locale based on the app locale.
    392    // NOTE: We do not user document.l10n to translate the survey banner.
    393    // Instead we only translate the banner into a limited set of locales that
    394    // match the languages that the survey itself supports. This should match
    395    // the language of the survey when it is opened by the user.
    396    for (const localeData of this._localeDataSet) {
    397      if (localeData.browserLocales.includes(this._locale)) {
    398        this._localeData = localeData;
    399        break;
    400      }
    401    }
    402    if (!this._localeData) {
    403      // Show the default en-US banner.
    404      this._localeData = this._localeDataSet[0];
    405    }
    406 
    407    // Make sure the survey's lang and dir attributes match the chosen locale.
    408    const surveyEl = document.getElementById("survey");
    409    surveyEl.setAttribute("lang", this._localeData.browserLocales[0]);
    410    surveyEl.setAttribute("dir", this._localeData.dir);
    411 
    412    const { heading, body, launch, dismiss, close } = this._localeData.strings;
    413 
    414    document.getElementById("survey-heading").textContent = heading;
    415    document.getElementById("survey-body").textContent = body;
    416    document.getElementById("survey-launch").textContent = launch;
    417    document.getElementById("survey-dismiss").textContent = dismiss;
    418    document.getElementById("survey-close").setAttribute("title", close);
    419 
    420    document.body.classList.add("show-survey");
    421  },
    422 };
    423 
    424 /**
    425 * Area for the Year End Campaign (YEC).
    426 * See tor-browser#42072.
    427 */
    428 const YecArea = {
    429  /**
    430   * The epoch time to start showing the banner, if at all.
    431   *
    432   * @type {?integer}
    433   */
    434  _startDate: null, // No YEC is active.
    435 
    436  /**
    437   * The epoch time to stop showing the banner, if at all.
    438   *
    439   * @type {?integer}
    440   */
    441  _endDate: null, // No YEC is active.
    442 
    443  /**
    444   * Whether the area has been initialised.
    445   *
    446   * @type {boolean}
    447   */
    448  _initialized: false,
    449 
    450  /**
    451   * The app locale, or `null` whilst unset.
    452   *
    453   * @type {?string}
    454   */
    455  _locale: null,
    456 
    457  /**
    458   * Whether the banner should be shown.
    459   *
    460   * @type {boolean}
    461   */
    462  _shouldShow: false,
    463 
    464  /**
    465   * The banner element.
    466   *
    467   * @type {?Element}
    468   */
    469  _areaEl: null,
    470 
    471  /**
    472   * Initialize the widget.
    473   */
    474  init() {
    475    this._initialized = true;
    476 
    477    this._areaEl = document.getElementById("yec-banner");
    478 
    479    document.getElementById("yec-close").addEventListener("click", () => {
    480      this.dismiss();
    481      dispatchEvent(new CustomEvent("UserDismissedYEC", { bubbles: true }));
    482    });
    483 
    484    this._update();
    485  },
    486 
    487  /**
    488   * Close the banner.
    489   */
    490  dismiss() {
    491    this._shouldShow = false;
    492    this._update();
    493  },
    494 
    495  /**
    496   * Possibly show the banner.
    497   *
    498   * @param {boolean} dismissYEC - Whether the user has dismissed YEC.
    499   * @param {boolean} isStable - Whether this is a stable release.
    500   * @param {string} appLocale - The app locale, as BCP47.
    501   */
    502  potentiallyShow(dismissYEC, isStable, appLocale) {
    503    const now = Date.now();
    504    this._shouldShow =
    505      !dismissYEC &&
    506      isStable &&
    507      this._startDate &&
    508      now >= this._startDate &&
    509      now < this._endDate;
    510    this._locale = appLocale;
    511    this._update();
    512  },
    513 
    514  /**
    515   * Update the visibility of the banner to reflect the new state.
    516   */
    517  _update() {
    518    if (!this._initialized) {
    519      return;
    520    }
    521    if (!this._shouldShow) {
    522      if (this._areaEl.contains(document.activeElement)) {
    523        document.documentElement.focus();
    524      }
    525      document.body.classList.remove("show-yec");
    526      return;
    527    }
    528 
    529    const donateLink = document.getElementById("yec-donate-link");
    530    const base = "https://www.torproject.org/donate";
    531    donateLink.href = base;
    532 
    533    document.body.classList.add("show-yec");
    534  },
    535 };
    536 
    537 let gInitialData = false;
    538 let gLoaded = false;
    539 
    540 function maybeComplete() {
    541  if (!gInitialData || !gLoaded) {
    542    return;
    543  }
    544  // Wait to show the content when the l10n population has completed.
    545  if (document.hasPendingL10nMutations) {
    546    window.addEventListener(
    547      "L10nMutationsFinished",
    548      () => {
    549        document.body.classList.add("initialized");
    550      },
    551      { once: true }
    552    );
    553  } else {
    554    document.body.classList.add("initialized");
    555  }
    556 }
    557 
    558 window.addEventListener("DOMContentLoaded", () => {
    559  SearchWidget.init();
    560  MessageArea.init();
    561  SurveyArea.init();
    562  YecArea.init();
    563 
    564  gLoaded = true;
    565  maybeComplete();
    566 });
    567 
    568 window.addEventListener("InitialData", event => {
    569  const {
    570    torConnectEnabled,
    571    isStable,
    572    searchOnionize,
    573    messageData,
    574    surveyDismissVersion,
    575    appLocale,
    576    dismissYEC,
    577  } = event.detail;
    578  SearchWidget.setOnionizeState(!!searchOnionize);
    579  MessageArea.setMessageData(messageData, !!isStable, !!torConnectEnabled);
    580  SurveyArea.potentiallyShow(surveyDismissVersion, isStable, appLocale);
    581  YecArea.potentiallyShow(dismissYEC, isStable, appLocale);
    582 
    583  gInitialData = true;
    584  maybeComplete();
    585 });
    586 
    587 window.addEventListener("DismissYEC", () => {
    588  // User closed the banner in another about:tor instance.
    589  YecArea.dismiss();
    590 });