tor-browser

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

requestBridgeDialog.js (6148B)


      1 "use strict";
      2 
      3 const { BridgeDB } = ChromeUtils.importESModule(
      4  "resource://gre/modules/BridgeDB.sys.mjs"
      5 );
      6 
      7 const { TorConnect, TorConnectStage, TorConnectTopics } =
      8  ChromeUtils.importESModule("resource://gre/modules/TorConnect.sys.mjs");
      9 
     10 const log = console.createInstance({
     11  maxLogLevel: "Warn",
     12  prefix: "requestBridgeDialog",
     13 });
     14 
     15 const gRequestBridgeDialog = {
     16  selectors: {
     17    dialogHeader: "#torPreferences-requestBridge-header",
     18    captchaImage: "image#torPreferences-requestBridge-captchaImage",
     19    captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
     20    refreshCaptchaButton:
     21      "button#torPreferences-requestBridge-refreshCaptchaButton",
     22    incorrectCaptchaHbox:
     23      "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
     24  },
     25 
     26  init() {
     27    this._result = window.arguments[0];
     28 
     29    const selectors = this.selectors;
     30 
     31    this._dialog = document.getElementById(
     32      "torPreferences-requestBridge-dialog"
     33    );
     34 
     35    // Add styling for tor-button to the dialog shadow root.
     36    const styleLink = document.createElement("link");
     37    styleLink.rel = "stylesheet";
     38    styleLink.href =
     39      "chrome://browser/content/torpreferences/torPreferences.css";
     40    this._dialog.shadowRoot.append(styleLink);
     41 
     42    // user may have opened a Request Bridge dialog in another tab, so update the
     43    // CAPTCHA image or close out the dialog if we have a bridge list
     44    this._dialog.addEventListener("focusin", () => {
     45      const uri = BridgeDB.currentCaptchaImage;
     46      const bridges = BridgeDB.currentBridges;
     47 
     48      // new captcha image
     49      if (uri) {
     50        this._setcaptchaImage(uri);
     51      } else if (bridges) {
     52        this._dialog.cancelDialog();
     53      }
     54    });
     55 
     56    this._submitButton = this._dialog.getButton("accept");
     57    this._submitButton.disabled = true;
     58    this._dialog.addEventListener("dialogaccept", e => {
     59      e.preventDefault();
     60      this.onSubmitCaptcha();
     61    });
     62 
     63    this._dialogHeader = this._dialog.querySelector(selectors.dialogHeader);
     64 
     65    this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
     66 
     67    // request captcha from bridge db
     68    BridgeDB.requestNewCaptchaImage().then(uri => {
     69      this._setcaptchaImage(uri);
     70    });
     71 
     72    this._captchaEntryTextbox = this._dialog.querySelector(
     73      selectors.captchaEntryTextbox
     74    );
     75    this._captchaEntryTextbox.disabled = true;
     76    // disable submit if entry textbox is empty
     77    this._captchaEntryTextbox.oninput = () => {
     78      this._submitButton.disabled = this._captchaEntryTextbox.value == "";
     79    };
     80 
     81    this._captchaRefreshButton = this._dialog.querySelector(
     82      selectors.refreshCaptchaButton
     83    );
     84    this._captchaRefreshButton.disabled = true;
     85    this._captchaRefreshButton.addEventListener("command", () => {
     86      this.onRefreshCaptcha();
     87    });
     88 
     89    this._incorrectCaptchaHbox = this._dialog.querySelector(
     90      selectors.incorrectCaptchaHbox
     91    );
     92 
     93    Services.obs.addObserver(this, TorConnectTopics.StageChange);
     94    this.onAcceptStateChange();
     95  },
     96 
     97  uninit() {
     98    BridgeDB.close();
     99    // Unregister our observer topics.
    100    Services.obs.removeObserver(this, TorConnectTopics.StageChange);
    101  },
    102 
    103  onAcceptStateChange() {
    104    const connect = TorConnect.stageName !== TorConnectStage.Bootstrapped;
    105    this._result.connect = connect;
    106    this._submitButton.setAttribute(
    107      "data-l10n-id",
    108      connect ? "bridge-dialog-button-connect2" : "bridge-dialog-button-submit2"
    109    );
    110    this._submitButton.classList.toggle("tor-button", connect);
    111  },
    112 
    113  observe(subject, topic) {
    114    switch (topic) {
    115      case TorConnectTopics.StageChange:
    116        this.onAcceptStateChange();
    117        break;
    118    }
    119  },
    120 
    121  _setcaptchaImage(uri) {
    122    if (!uri) {
    123      return;
    124    }
    125    if (uri != this._captchaImage.src) {
    126      this._captchaImage.src = uri;
    127      this._dialogHeader.setAttribute(
    128        "data-l10n-id",
    129        "request-bridge-dialog-top-solve"
    130      );
    131      this._setUIDisabled(false);
    132      this._captchaEntryTextbox.focus();
    133      this._captchaEntryTextbox.select();
    134    }
    135  },
    136 
    137  _setUIDisabled(disabled) {
    138    this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled;
    139    this._captchaEntryTextbox.disabled = disabled;
    140    this._captchaRefreshButton.disabled = disabled;
    141  },
    142 
    143  _captchaGuessIsEmpty() {
    144    return this._captchaEntryTextbox.value == "";
    145  },
    146 
    147  /*
    148    Event Handlers
    149  */
    150  onSubmitCaptcha() {
    151    let captchaText = this._captchaEntryTextbox.value.trim();
    152    // noop if the field is empty
    153    if (captchaText == "") {
    154      return;
    155    }
    156 
    157    // freeze ui while we make request
    158    this._setUIDisabled(true);
    159    this._incorrectCaptchaHbox.style.visibility = "hidden";
    160 
    161    BridgeDB.submitCaptchaGuess(captchaText)
    162      .then(aBridges => {
    163        if (aBridges && aBridges.length) {
    164          this._result.accepted = true;
    165          this._result.bridges = aBridges;
    166          this._submitButton.disabled = false;
    167          // This was successful, but use cancelDialog() to close, since
    168          // we intercept the `dialogaccept` event.
    169          this._dialog.cancelDialog();
    170        } else {
    171          this._setUIDisabled(false);
    172          this._incorrectCaptchaHbox.style.visibility = "visible";
    173        }
    174      })
    175      .catch(aError => {
    176        // TODO: handle other errors properly here when we do the bridge settings re-design
    177        this._setUIDisabled(false);
    178        this._incorrectCaptchaHbox.style.visibility = "visible";
    179        log.error(aError);
    180      });
    181  },
    182 
    183  onRefreshCaptcha() {
    184    this._setUIDisabled(true);
    185    this._captchaImage.src = "";
    186    this._dialogHeader.setAttribute(
    187      "data-l10n-id",
    188      "request-bridge-dialog-top-wait"
    189    );
    190    this._captchaEntryTextbox.value = "";
    191    this._incorrectCaptchaHbox.style.visibility = "hidden";
    192 
    193    BridgeDB.requestNewCaptchaImage().then(uri => {
    194      this._setcaptchaImage(uri);
    195    });
    196  },
    197 };
    198 
    199 window.addEventListener(
    200  "DOMContentLoaded",
    201  () => {
    202    gRequestBridgeDialog.init();
    203    window.addEventListener(
    204      "unload",
    205      () => {
    206        gRequestBridgeDialog.uninit();
    207      },
    208      { once: true }
    209    );
    210  },
    211  { once: true }
    212 );