Commit 550bff23 authored by henry's avatar henry Committed by morgan
Browse files

fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in...

fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection

TB 45058: Convert bridge settings to use config.
parent 0b37b3e9
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -271,7 +271,7 @@ const CONFIG_PANES = Object.freeze({
  connection: {
    l10nId: "tor-connection-settings-pane",
    iconSrc: "chrome://browser/content/torconnect/tor-connect.svg",
    groupIds: ["connectionStatus"],
    groupIds: ["connectionStatus", "torBridges"],
    module: "chrome://browser/content/torpreferences/config/connection.mjs",
    visible: () => {
      return TorConnect.enabled;
+1 −0
Original line number Diff line number Diff line
@@ -108,6 +108,7 @@
  <script type="module" src="chrome://browser/content/preferences/widgets/update-information.mjs"></script>
  <script type="module" src="chrome://browser/content/preferences/widgets/update-state.mjs"></script>
  <script type="module" src="chrome://browser/content/ipprotection/bandwidth-usage.mjs"></script>
  <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-bridges-display.mjs"></script>
  <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-connection-status.mjs"></script>
  <script src="chrome://browser/content/torpreferences/bridgemoji/BridgeEmoji.js"/>
</head>
+413 −0
Original line number Diff line number Diff line
@@ -4,11 +4,33 @@ import { Preferences } from "chrome://global/content/preferences/Preferences.mjs
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  InternetStatus: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
  openBridgeDialog:
    "chrome://browser/content/torpreferences/config/helpers.mjs",
  openUserProvideBridgeDialog:
    "chrome://browser/content/torpreferences/config/helpers.mjs",
  TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
  TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
  TorConnectStage: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
  TorConnectTopics: "moz-src:///toolkit/modules/TorConnect.sys.mjs",
  TorProviderBuilder:
    "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
  TorProviderState:
    "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
  TorProviderTopics:
    "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs",
  TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
  TorSettingsTopics: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
});

// TODO: Change to GetLoxBridges if Lox enabled, and the account is set up.
const TELEGRAM_USER_NAME = "GetBridgesBot";
const TELEGRAM_HREF = `https://t.me/${TELEGRAM_USER_NAME}`;

const TOR_BRIDGES_URL_NAME = "bridges.torproject.org";
const TOR_BRIDGES_HREF = "https://bridges.torproject.org";

const TOR_BRIDGES_EMAIL = "bridges@torproject.org";

SettingGroupManager.registerGroups({
  connectionStatus: {
    inProgress: true,
@@ -39,6 +61,123 @@ SettingGroupManager.registerGroups({
      },
    ],
  },
  torBridges: {
    inProgress: true,
    l10nId: "tor-bridges-group",
    supportPage: "tor-manual:bridges",
    headingLevel: 2,
    controlAttrs: { "focusable-heading": true },
    items: [
      {
        id: "bridgesEnabled",
        l10nId: "tor-bridges-use-bridges",
        control: "moz-toggle",
      },
      {
        id: "torBridgesDisplay",
        control: "tor-bridges-display",
      },
      {
        id: "newBridgesGroup",
        control: "moz-fieldset",
        controlAttrs: {
          headinglevel: 3,
        },
        options: [
          {
            control: "moz-box-group",
            items: [
              {
                id: "builtinBridges",
                l10nId: "tor-bridges-choose-built-in-button",
                control: "moz-box-button",
              },
              {
                id: "userProvidedBridges",
                l10nId: "tor-bridges-enter-bridges-button",
                control: "moz-box-button",
              },
            ],
          },
        ],
      },
      {
        id: "findMoreBridgesGroup",
        l10nId: "tor-bridges-find-more-group",
        control: "moz-fieldset",
        controlAttrs: {
          headinglevel: 3,
        },
        options: [
          {
            control: "moz-box-item",
            options: [
              {
                id: "torBridgesRequestBanner",
                control: "article",
                options: [
                  {
                    control: "img",
                    controlAttrs: {
                      alt: "",
                      src: "chrome://browser/content/torpreferences/bridge-bot.svg",
                    },
                  },
                  {
                    control: "p",
                    l10nId: "tor-bridges-request-from-browser2",
                  },
                  {
                    // NOTE: We use the wrapping `div` to simply switch from the
                    // `options` context to the `items` context, with the latter
                    // wrapping the elements in a setting-control.
                    control: "div",
                    items: [
                      {
                        id: "requestBridges",
                        l10nId: "tor-bridges-request-button2",
                        control: "moz-button",
                      },
                    ],
                  },
                ],
              },
            ],
          },
          {
            control: "moz-box-group",
            options: [
              {
                l10nId: "tor-bridges-source-telegram-link",
                l10nArgs: { telegramUserName: TELEGRAM_USER_NAME },
                control: "moz-box-link",
                iconSrc:
                  "chrome://browser/content/torpreferences/telegram-logo.svg",
                controlAttrs: {
                  href: TELEGRAM_HREF,
                },
              },
              {
                l10nId: "tor-bridges-source-web-link",
                l10nArgs: { url: TOR_BRIDGES_URL_NAME },
                control: "moz-box-link",
                iconSrc: "chrome://browser/content/torconnect/network.svg",
                controlAttrs: {
                  href: TOR_BRIDGES_HREF,
                },
              },
              {
                l10nId: "tor-bridges-source-email-link",
                l10nArgs: { address: TOR_BRIDGES_EMAIL },
                control: "moz-box-item",
                iconSrc: "chrome://browser/content/torpreferences/mail.svg",
              },
            ],
          },
        ],
      },
    ],
  },
});

Preferences.addSetting({
@@ -113,3 +252,277 @@ Preferences.addSetting({
    lazy.TorConnect.quickstart = val;
  },
});

Preferences.addSetting({
  id: "torSettingsReady",
  _ready: false,
  setup(emitChange) {
    if (!lazy.TorSettings.enabled) {
      // Remain in `false`.
      return;
    }
    // Most likely, TorSettings will already be initialised.
    if (lazy.TorSettings.initialized) {
      this._ready = true;
      return;
    }
    // Else, wait for it to be initialised.
    lazy.TorSettings.initializedPromise.then(
      () => {
        this._ready = true;
        emitChange();
      },
      error => {
        // No change in state.
        console.error("TorSettings failed to initialize.", error);
      }
    );
  },
  get() {
    return this._ready;
  },
});

Preferences.addSetting({
  id: "torBridgesRaw",
  deps: ["torSettingsReady"],
  _value: null,
  setup(emitChange) {
    const observer = subject => {
      const { changes } = subject.wrappedJSObject;
      // NOTE: We do not include "bridges.lox_id" in the changes. Instead, any
      // widgets should wait for LoxTopics.UpdateActiveLoxId to ensure that the
      // Lox module has responded to the change in ID strictly *before* we do.
      // In particular, we want to make sure the invites and event data has been
      // cleared.
      if (
        changes.includes("bridges.source") ||
        changes.includes("bridges.bridge_strings") ||
        changes.includes("bridges.builtin_type")
      ) {
        // Reset.
        this._value = null;
        emitChange();
      }
    };
    Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged);
    return () => {
      Services.obs.removeObserver(
        observer,
        lazy.TorSettingsTopics.SettingsChanged
      );
    };
  },
  get(_pref, { torSettingsReady }) {
    if (this._value === null) {
      if (!torSettingsReady.value) {
        // TorSettings getter will throw.
        return null;
      }
      const source = lazy.TorSettings.bridges.source;
      // Cache a value.
      this._value = {
        haveBridges: source !== lazy.TorBridgeSource.Invalid,
        source,
        builtinType: lazy.TorSettings.bridges.builtin_type,
        bridgeStrings: lazy.TorSettings.bridges.bridge_strings,
      };
    }
    return this._value;
  },
});

Preferences.addSetting({
  id: "connectedBridgeId",
  _value: null,
  setup(emitChange) {
    const observer = async () => {
      // NOTE: It should be safe for this method to be called concurrently.
      let bridge = null;
      try {
        if (
          lazy.TorProviderBuilder.currentState() ===
          lazy.TorProviderState.Running
        ) {
          bridge = (await lazy.TorProviderBuilder.build()).currentBridge;
        }
        // Else, bridge is `null` whilst the provider is not running.
      } catch (e) {
        console.warn("Could not get current bridge", e);
      }
      const prevVal = this._value;
      this._value = bridge?.fingerprint ?? null;
      if (prevVal !== this._value) {
        emitChange();
      }
    };
    Services.obs.addObserver(observer, lazy.TorProviderTopics.BridgeChanged);
    // NOTE: BridgeChanged is only fired directly by the provider instances,
    // rather than by TorProviderBuilder. In particular, it will not fire when
    // the previous provider had a bridge and the new one does not, because
    // neither provider saw a change in their own bridge. But from the user's
    // point of view, the overall change would mean that the current bridge has
    // changed.
    // Moreover, we want to show no connected bridge whilst we are missing a
    // provider. Therefore, we also need to listen for a change in provider and
    // its state.
    // TODO: Maybe this logic should be moved to TorProviderBuilder itself if it
    // is ever needed by other parts of the UI.
    Services.obs.addObserver(
      observer,
      lazy.TorProviderTopics.ProviderStateChanged
    );
    // Get the initial value.
    observer();

    return () => {
      Services.obs.removeObserver(
        observer,
        lazy.TorProviderTopics.ProviderStateChanged
      );
      Services.obs.removeObserver(
        observer,
        lazy.TorProviderTopics.BridgeChanged
      );
    };
  },
  get() {
    return this._value;
  },
});

Preferences.addSetting({
  id: "bridgesEnabled",
  deps: ["torSettingsReady", "torBridgesRaw"],
  setup(emitChange) {
    const observer = subject => {
      const { changes } = subject.wrappedJSObject;
      if (changes.includes("bridges.enabled")) {
        emitChange();
      }
    };
    Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged);
    return () => {
      Services.obs.removeObserver(
        observer,
        lazy.TorSettingsTopics.SettingsChanged
      );
    };
  },
  get(_prefVal, { torSettingsReady }) {
    if (!torSettingsReady.value) {
      // TorSettings.bridges will throw before TorSettings has finished
      // initialisation.
      return false;
    }
    return lazy.TorSettings.bridges.enabled;
  },
  set(val) {
    lazy.TorSettings.changeSettings({
      bridges: { enabled: val },
    });
  },
  visible({ torSettingsReady }) {
    return torSettingsReady.value;
  },
  disabled({ torBridgesRaw }) {
    return !torBridgesRaw.value?.haveBridges;
  },
});

Preferences.addSetting({
  id: "torBridgesDisplay",
  deps: ["torSettingsReady", "torBridgesRaw", "connectedBridgeId"],
  getControlConfig(config, { torBridgesRaw, connectedBridgeId }) {
    config.controlAttrs = {
      ...config.controlAttrs,
      // Set the `bridges` and `connnectedBridgeId` object *properties* (rather
      // than attributes) by using the `.` prefix.
      ".bridges": torBridgesRaw.value,
      ".connectedBridgeId": connectedBridgeId.value,
    };
    return config;
  },
  visible({ torSettingsReady }) {
    return torSettingsReady.value;
  },
});

Preferences.addSetting({
  id: "newBridgesGroup",
  deps: ["torSettingsReady", "torBridgesRaw"],
  getControlConfig(config, { torBridgesRaw }) {
    config.l10nId = torBridgesRaw.value?.haveBridges
      ? "tor-bridges-replace-bridges-group"
      : "tor-bridges-add-bridges-group";
    return config;
  },
  visible({ torSettingsReady }) {
    return torSettingsReady.value;
  },
});

Preferences.addSetting({
  id: "builtinBridges",
  onUserClick() {
    lazy.openBridgeDialog(
      window,
      "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml",
      null,
      result => {
        if (!result.type) {
          return null;
        }
        return lazy.TorSettings.changeSettings({
          bridges: {
            enabled: true,
            source: lazy.TorBridgeSource.BuiltIn,
            builtin_type: result.type,
          },
        });
      }
    );
  },
});

Preferences.addSetting({
  id: "userProvidedBridges",
  deps: ["torBridgesRaw"],
  onUserClick(_event, { torBridgesRaw }) {
    lazy.openUserProvideBridgeDialog(
      window,
      torBridgesRaw.value?.haveBridges ? "replace" : "add"
    );
  },
});

Preferences.addSetting({
  id: "findMoreBridgesGroup",
  deps: ["torSettingsReady"],
  visible({ torSettingsReady }) {
    return torSettingsReady.value;
  },
});

Preferences.addSetting({
  id: "requestBridges",
  onUserClick() {
    lazy.openBridgeDialog(
      window,
      "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
      null,
      result => {
        if (!result.bridges?.length) {
          return null;
        }
        return lazy.TorSettings.changeSettings({
          bridges: {
            enabled: true,
            source: lazy.TorBridgeSource.BridgeDB,
            bridge_strings: result.bridges,
          },
        });
      }
    );
  },
});
+125 −0
Original line number Diff line number Diff line
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
  TorConnectParent:
    "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs",
  TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs",
});

/**
 * Force the focus to move to the bridge heading.
 *
 * @param {Window} win - The preferences window.
 * @param {boolean} [forceTopHeading=false] - Force the focus to move to the
 *   top "Bridges" setting heading.
 */
export function moveFocusToBridgeHeading(win, forceTopHeading = false) {
  // Move focus to the start of the relevant section, which is a heading.
  // They have tabindex="-1" so should be focusable, even though they are not
  // part of the usual tab navigation.
  // TODO: It might be better if we could use the # named anchor to
  // re-orient the screen reader position instead of using tabIndex=-1, but
  // about:preferences currently uses the anchor for showing categories
  // only. See bugzilla bug 1799153.
  if (
    forceTopHeading ||
    !win.document.getElementById("torBridgesDisplay").focusHeading()
  ) {
    win.document
      .querySelector('setting-group[groupid="torBridges"] moz-fieldset')
      .focusHeading();
  }
}

/**
 * Open a bridge dialog that will change the users bridges.
 *
 * @param {Window} win - The preferences window.
 * @param {string} url - The url of the dialog to open.
 * @param {object?} inputData - The input data to send to the dialog window.
 * @param {Function} onAccept - The method to call if the bridge dialog was
 *   accepted by the user. This will be passed a "result" object containing
 *   data set by the dialog. This should return a promise that resolves once
 *   the bridge settings have been set, or null if the settings have not
 *   been applied.
 */
export function openBridgeDialog(win, url, inputData, onAccept) {
  const result = { accepted: false, connect: false };
  let savedSettings = null;
  win.gSubDialog.open(
    url,
    {
      features: "resizable=yes",
      closingCallback: () => {
        if (!result.accepted) {
          return;
        }
        savedSettings = onAccept(result);
        if (!savedSettings) {
          // No change in settings.
          return;
        }
        if (!result.connect) {
          // Do not open about:torconnect.
          return;
        }

        // Wait until the settings are applied before bootstrapping.
        // NOTE: Saving the settings should also cancel any existing bootstrap
        // attempt first. See tor-browser#41921.
        savedSettings.then(() => {
          // The bridge dialog button is "connect" when Tor is not
          // bootstrapped, so do the connect.

          // Start Bootstrapping, which should use the configured bridges.
          // NOTE: We do this regardless of any previous TorConnect Error.
          lazy.TorConnectParent.open({ beginBootstrapping: "hard" });
        });
      },
      // closedCallback should be called after gSubDialog has already
      // re-assigned focus back to the document.
      closedCallback: () => {
        if (!savedSettings) {
          return;
        }
        // Wait until the settings have changed, so that the UI could
        // respond, then move focus.
        savedSettings.then(() => {
          moveFocusToBridgeHeading(win);
        });
      },
    },
    result,
    inputData
  );
}

/**
 * Open the user provide dialog.
 *
 * @param {Window} win - The preferences window.
 * @param {string} mode - The mode to open the dialog in: "add", "replace" or
 *   "edit".
 */
export function openUserProvideBridgeDialog(win, mode) {
  openBridgeDialog(
    win,
    "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml",
    { mode },
    result => {
      const loxId = result.loxId;
      if (!loxId && !result.addresses?.length) {
        return null;
      }
      const bridges = { enabled: true };
      if (loxId) {
        bridges.source = lazy.TorBridgeSource.Lox;
        bridges.lox_id = loxId;
      } else {
        bridges.source = lazy.TorBridgeSource.UserProvided;
        bridges.bridge_strings = result.addresses;
      }
      return lazy.TorSettings.changeSettings({ bridges });
    }
  );
}
+74 −100
Original line number Diff line number Diff line
<html:template id="tor-bridges-display-template">
  <html:div id="tor-bridges-none" hidden="hidden">
    <html:img id="tor-bridges-none-icon" alt="" />
    <html:div data-l10n-id="tor-bridges-none-added"></html:div>
    <html:p data-l10n-id="tor-bridges-none-added"></html:p>
  </html:div>
  <html:div id="tor-bridges-current" class="tor-bridges-box" hidden="hidden">
    <html:div id="tor-bridges-current-header-bar">
      <html:h2
  <html:fieldset id="tor-bridges-current" hidden="hidden">
    <html:legend>
      <html:h4
        id="tor-bridges-current-heading-non-search"
        class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-non-search-heading"
        tabindex="-1"
        data-l10n-id="tor-bridges-your-bridges"
      ></html:h2>
      <!-- Add a duplicate search heading.
         - In a search result the heading h1.tor-bridges-subcategory-heading
         - will be hidden, and the h2.tor-bridges-subcategory-heading
         - will be visible.
         - As such, all headings below h2.tor-bridges-subcategory-heading also
         - need to shift one lower in heading level to preseve the correct
         - hierarchy of - heading levels.
         - In this case we hide the <h2> heading and show the duplicate <h3>
         - heading instead.
         - See tor-browser#43320. -->
      <html:h3
        class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-search-heading"
        class="tor-bridges-current-heading tor-focusable-heading tor-small-heading"
        tabindex="-1"
        data-l10n-id="tor-bridges-your-bridges"
      ></html:h3>
      <html:span
      ></html:h4>
    </html:legend>
    <html:p
      id="tor-bridges-user-label"
      class="tor-bridges-source-label"
      data-l10n-id="tor-bridges-source-user"
      ></html:span>
      <html:span
    ></html:p>
    <html:p
      id="tor-bridges-built-in-label"
      class="tor-bridges-source-label"
      data-l10n-id="tor-bridges-source-built-in"
      ></html:span>
      <html:span
    ></html:p>
    <html:p
      id="tor-bridges-requested-label"
      class="tor-bridges-source-label"
      data-l10n-id="tor-bridges-source-requested"
      ></html:span>
      <html:span id="tor-bridges-lox-label" class="tor-bridges-source-label">
    ></html:p>
    <html:p id="tor-bridges-lox-label" class="tor-bridges-source-label">
      <html:img id="tor-bridges-lox-label-icon" alt="" />
      <html:span data-l10n-id="tor-bridges-source-lox"></html:span>
      </html:span>
    </html:p>
    <html:button
      id="tor-bridges-all-options-button"
      class="tor-bridges-options-button"
@@ -77,19 +64,15 @@
        data-l10n-id="tor-bridges-menu-item-remove-all-bridges"
      ></html:panel-item>
    </html:panel-list>
    </html:div>
    <html:div id="tor-bridges-built-in-display" hidden="hidden">
      <html:div id="tor-bridges-built-in-type-name"></html:div>
      <html:div
        id="tor-bridges-built-in-connected"
        class="bridge-status-badge"
      >
      <html:p id="tor-bridges-built-in-type-name"></html:p>
      <html:p id="tor-bridges-built-in-connected" class="bridge-status-badge">
        <html:div class="bridge-status-icon"></html:div>
        <html:span
          data-l10n-id="tor-bridges-built-in-status-connected"
        ></html:span>
      </html:div>
      <html:div id="tor-bridges-built-in-description"></html:div>
      </html:p>
      <html:p id="tor-bridges-built-in-description"></html:p>
    </html:div>
    <html:div
      id="tor-bridges-grid-display"
@@ -161,19 +144,14 @@
      class="tor-bridges-details-box"
      hidden="hidden"
    >
      <html:h3
        class="tor-bridges-share-heading tor-small-heading tor-non-search-heading"
      <html:h5
        class="tor-bridges-share-heading tor-small-heading"
        data-l10n-id="tor-bridges-share-heading"
      ></html:h3>
      <!-- Add a duplicate search heading. See tor-browser#43320. -->
      <html:h4
        class="tor-bridges-share-heading tor-small-heading tor-search-heading"
        data-l10n-id="tor-bridges-share-heading"
      ></html:h4>
      <html:span
      ></html:h5>
      <html:p
        id="tor-bridges-share-description"
        data-l10n-id="tor-bridges-share-description"
      ></html:span>
      ></html:p>
      <html:button
        id="tor-bridges-copy-addresses-button"
        data-l10n-id="tor-bridges-copy-addresses-button"
@@ -192,15 +170,10 @@
      >
        <html:img alt="" class="tor-bridges-lox-image-inner" />
        <html:img alt="" class="tor-bridges-lox-image-outer" />
        <html:h3
          class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-non-search-heading"
          tabindex="-1"
        ></html:h3>
        <!-- Add a duplicate search heading. See tor-browser#43320. -->
        <html:h4
          class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-search-heading"
        <html:h5
          class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading"
          tabindex="-1"
        ></html:h4>
        ></html:h5>
        <html:ul class="tor-bridges-lox-list">
          <html:li
            id="tor-bridges-lox-next-unlock-gain-bridges"
@@ -270,7 +243,8 @@
        ></html:button>
      </html:div>
    </html:div>
  </html:div>
  </html:fieldset>
</html:template>

<!-- Tor panel -->

Loading