morgan pushed to branch tor-browser-153.0esr-16.0-1 at The Tor Project / Applications / Tor Browser Commits: 6aa05c11 by Henry Wilkes at 2026-08-06T19:42:53+01:00 fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection TB 45196: Replace old connection settings with setting-group elements. These can eventually be dropped entirely after we switch to the settings redesign, but this allows us to do a lot of tidying in advance. - - - - - 6054bf5a by Henry Wilkes at 2026-08-06T19:42:55+01:00 fixup! Tor Browser strings TB 45196: Drop old strings. - - - - - 492c55b8 by Henry Wilkes at 2026-08-06T19:42:55+01:00 fixup! Add TorStrings module for localization TB 45196: Drop some old strings. - - - - - 9 changed files: - browser/components/preferences/preferences.js - browser/components/torpreferences/config/connection.mjs - browser/components/torpreferences/content/connectionPane.inc.xhtml - − browser/components/torpreferences/content/connectionPane.js - browser/components/torpreferences/content/torPreferences.css - browser/components/torpreferences/jar.mn - toolkit/locales/en-US/toolkit/global/tor-browser.ftl - toolkit/modules/TorStrings.sys.mjs - toolkit/torbutton/chrome/locale/en-US/settings.properties Changes: ===================================== browser/components/preferences/preferences.js ===================================== @@ -12,7 +12,6 @@ /* import-globals-from findInPage.js */ /* import-globals-from /browser/base/content/utilityOverlay.js */ /* import-globals-from /toolkit/content/preferencesBindings.js */ -/* import-globals-from ../torpreferences/content/connectionPane.js */ /** @import MozButton from "chrome://global/content/elements/moz-button.mjs" */ /** @import {SettingConfig, SettingEmitChange} from "chrome://global/content/preferences/Setting.mjs" */ @@ -116,7 +115,7 @@ ChromeUtils.defineESModuleGetters(this, { "resource:///modules/SelectionChangedMenulist.sys.mjs", ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs", SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs", - TorConnect: "resource://gre/modules/TorConnect.sys.mjs", + TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TransientPrefs: "resource:///modules/TransientPrefs.sys.mjs", UIState: "resource://services-sync/UIState.sys.mjs", UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs", @@ -574,12 +573,22 @@ function init_all() { register_module("paneSync", gSyncPane); } register_module("paneSearchResults", gSearchResultsPane); - if (gConnectionPane.enabled) { - document.getElementById("category-connection").hidden = false; - register_module("paneConnection", gConnectionPane); - } else { - // Remove the pane from the DOM so it doesn't get incorrectly included in search results. - document.getElementById("template-paneConnection").remove(); + if (!redesignEnabled) { + if (TorConnect.enabled) { + register_module("paneConnection", { + init() { + ChromeUtils.importESModule( + "chrome://browser/content/torpreferences/config/connection.mjs", + { global: "current" } + ); + initSettingGroup("connectionStatus"); + initSettingGroup("torBridges"); + initSettingGroup("torAdvanced"); + }, + }); + } else { + document.getElementById("category-connection").remove(); + } } for (let [id, config] of Object.entries(CONFIG_PANES)) { // Skip over configs we do not want, including all its children. ===================================== browser/components/torpreferences/config/connection.mjs ===================================== @@ -35,7 +35,6 @@ const TOR_BRIDGES_EMAIL = "bridges@torproject.org"; SettingGroupManager.registerGroups({ connectionStatus: { - inProgress: true, l10nId: "tor-connection-internet-status-group", supportPage: "tor-manual:getting-started__about-tor-browser", headingLevel: 2, @@ -64,7 +63,6 @@ SettingGroupManager.registerGroups({ ], }, torBridges: { - inProgress: true, l10nId: "tor-bridges-group", supportPage: "tor-manual:bridges", headingLevel: 2, @@ -193,7 +191,6 @@ SettingGroupManager.registerGroups({ ], }, torAdvanced: { - inProgress: true, l10nId: "tor-advanced-group", headingLevel: 2, items: [ ===================================== browser/components/torpreferences/content/connectionPane.inc.xhtml ===================================== @@ -246,642 +246,30 @@ </html:fieldset> </html:template> -<!-- Tor panel --> - -<script - type="application/javascript" - src="chrome://browser/content/torpreferences/connectionPane.js" +<hbox + id="torPreferencesCategory" + class="subcategory" + data-category="paneConnection" + hidden="true" + data-srd-groupid="connectionStatus" +> + <html:h1 data-l10n-id="tor-connection-settings-heading"></html:h1> +</hbox> +<html:setting-group + groupid="connectionStatus" + data-category="paneConnection" + hidden="hidden" + data-srd-migrated="" +/> +<html:setting-group + groupid="torBridges" + data-category="paneConnection" + hidden="hidden" + data-srd-migrated="" +/> +<html:setting-group + groupid="torAdvanced" + data-category="paneConnection" + hidden="hidden" + data-srd-migrated="" /> -<html:template id="template-paneConnection"> - <vbox - id="torPreferencesCategory" - class="subcategory" - data-category="paneConnection" - hidden="true" - data-srd-groupid="connectionStatus" - > - <html:h1 data-l10n-id="tor-connection-settings-heading"></html:h1> - <description class="description-deemphasized" flex="1"> - <html:span data-l10n-id="tor-connection-overview"></html:span> - <label - class="learnMore text-link" - is="text-link" - href="about:manual#getting-started__about-tor-browser" - useoriginprincipal="true" - data-l10n-id="tor-connection-browser-learn-more-link" - /> - </description> - <!-- Keep within #torPreferencesCategory so this won't appear in search - - results. --> - <html:div - id="network-status-internet-area" - class="network-status-area" - role="group" - aria-labelledby="network-status-internet-area-label" - > - <html:img alt="" class="network-status-icon" /> - <!-- NOTE: We do not wrap the label and status ("Internet: Offline", etc) - - in an aria-live area because it may be too noisey and may not be - - important to the user. --> - <html:span - id="network-status-internet-area-label" - class="network-status-label" - data-l10n-id="tor-connection-internet-status-label" - ></html:span> - <html:span class="network-status-result"></html:span> - </html:div> - <html:div - id="network-status-tor-area" - class="network-status-area" - role="group" - aria-labelledby="network-status-tor-area-label" - > - <html:img alt="" class="network-status-icon" /> - <!-- NOTE: We do not wrap the label and status - - ("Tor network: Not connected", etc) in an aria-live area. - - This is not likely to change whilst this page has focus. - - Moreover, the status is already present in the torconnect status bar - - in the window tab bar. --> - <html:span - id="network-status-tor-area-label" - class="network-status-label" - data-l10n-id="tor-connection-network-status-label" - ></html:span> - <html:span class="network-status-result"></html:span> - <html:button - id="network-status-tor-connect-button" - data-l10n-id="tor-connection-network-status-connect-button" - ></html:button> - </html:div> - </vbox> - - <!-- Quickstart --> - <groupbox - data-category="paneConnection" - hidden="true" - data-srd-groupid="connectionStatus" - > - <label> - <html:h2 data-l10n-id="tor-connection-automatic-heading"></html:h2> - </label> - <description - class="description-deemphasized" - flex="1" - data-l10n-id="tor-connection-automatic-description" - /> - <html:moz-toggle - id="tor-connection-quickstart-toggle" - class="tor-toggle" - data-l10n-id="tor-connection-quickstart-checkbox" - data-l10n-attrs="label" - /> - </groupbox> - - <!-- Bridges --> - <hbox - class="subcategory" - data-category="paneConnection" - hidden="true" - data-srd-groupid="connectionStatus" - > - <html:h1 - id="tor-bridges-subcategory-heading-non-search" - class="tor-bridges-subcategory-heading tor-focusable-heading" - tabindex="-1" - data-l10n-id="tor-bridges-heading" - ></html:h1> - </hbox> - <groupbox - id="torPreferences-bridges-group" - data-category="paneConnection" - hidden="true" - aria-labelledby="tor-bridges-subcategory-heading-non-search" - data-srd-groupid="connectionStatus" - > - <!-- Add a search-header that only appears in search results as a substitute - - for the hidden h1 element. See tor-browser#43320. - - NOTE: Usually the first xul:label will act as the accessible name for - - a xul:groubbox element *if* it is not hidden. Since the search-header - - is sometimes hidden we need an explicit aria-labelledby anyway. - - However, we keep the wrapper xul:label for styling consistency with the - - other settings. --> - <label class="search-header" hidden="true"> - <html:h2 - class="tor-bridges-subcategory-heading tor-focusable-heading" - tabindex="-1" - data-l10n-id="tor-bridges-heading" - ></html:h2> - </label> - <description class="description-deemphasized" flex="1"> - <html:span data-l10n-id="tor-bridges-overview"></html:span> - <label - class="learnMore text-link" - is="text-link" - href="about:manual#circumvention__unblocking-tor" - useoriginprincipal="true" - data-l10n-id="tor-bridges-learn-more-link" - /> - </description> - <hbox - align="center" - id="torPreferences-bridges-locationGroup" - hidden="true" - > - <label - id="torPreferences-bridges-locationLabel" - control="torPreferences-bridges-location" - /> - <spacer flex="1" /> - <menulist id="torPreferences-bridges-location"> - <menupopup id="torPreferences-bridges-locationEntries" /> - </menulist> - <button - id="torPreferences-bridges-buttonChooseBridgeForMe" - class="primary tor-button" - /> - </hbox> - <html:moz-toggle - id="tor-bridges-enabled-toggle" - class="tor-toggle" - data-l10n-id="tor-bridges-use-bridges" - data-l10n-attrs="label" - /> - <!-- Add an aria-live area where we can post notifications to screen - - reader users about changes to their list of bridges. This is to give - - these users some feedback for when the remove a bridge or change - - their bridges in other ways. I.e. whenever tor-bridges-grid-display - - changes its rows. - - - - If we change the text in #tor-bridges-update-area-text, a screen - - reader should speak out the text to the user, even when this area - - does not have focus. - - - - In fact, we don't really want the user to navigate to this element - - directly. But currently using an aria-live region in the DOM is the - - only way to effectively pass a notification to a screen reader user. - - Since it must be somewhere in the DOM, we logically place it just - - before the grid, where it is hopefully least confusing to stumble - - across. - - - - TODO: Instead of aria-live in the DOM, use the proposed ariaNotify - - API if it gets accepted into firefox and works with screen readers. - - See https://github.com/WICG/proposals/issues/112 - --> - <!-- NOTE: This area is hidden by default, and is only shown temporarily - - when a notification is added. It should never match with search - - queries. --> - <html:div - id="tor-bridges-update-area" - hidden="hidden" - data-hidden-from-search="true" - > - <!-- NOTE: This first span's text content will *not* be read out as part - - of the notification because it does not have an aria-live - - attribute. Instead it is just here to give context to the following - - text in #tor-bridges-update-area-text if the user navigates to - - #tor-bridges-update-area manually whilst it is not hidden. - - I.e. it is just here to make it less confusing if a screen reader - - user stumbles across this. - --> - <html:span data-l10n-id="tor-bridges-update-area-intro"></html:span> - <!-- Whitespace between spans. --> - <!-- This second span is the area to place notification text in. --> - <html:span - id="tor-bridges-update-area-text" - aria-live="polite" - ></html:span> - </html:div> - <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:div> - <html:div id="tor-bridges-current" class="tor-bridges-box" hidden="hidden"> - <html:div id="tor-bridges-current-header-bar"> - <html:h2 - 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" - tabindex="-1" - data-l10n-id="tor-bridges-your-bridges" - ></html:h3> - <html:span - id="tor-bridges-user-label" - class="tor-bridges-source-label" - data-l10n-id="tor-bridges-source-user" - ></html:span> - <html:span - id="tor-bridges-built-in-label" - class="tor-bridges-source-label" - data-l10n-id="tor-bridges-source-built-in" - ></html:span> - <html:span - 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:img id="tor-bridges-lox-label-icon" alt="" /> - <html:span data-l10n-id="tor-bridges-source-lox"></html:span> - </html:span> - <html:button - id="tor-bridges-all-options-button" - class="tor-bridges-options-button" - aria-haspopup="menu" - aria-expanded="false" - aria-controls="tor-bridges-all-options-menu" - data-l10n-id="tor-bridges-options-button" - ></html:button> - <html:panel-list - id="tor-bridges-all-options-menu" - data-hidden-from-search="true" - > - <html:panel-item - id="tor-bridges-options-qr-all-menu-item" - data-l10n-attrs="accesskey" - data-l10n-id="tor-bridges-menu-item-qr-all-bridge-addresses" - ></html:panel-item> - <html:panel-item - id="tor-bridges-options-copy-all-menu-item" - data-l10n-attrs="accesskey" - data-l10n-id="tor-bridges-menu-item-copy-all-bridge-addresses" - ></html:panel-item> - <html:panel-item - id="tor-bridges-options-edit-all-menu-item" - data-l10n-attrs="accesskey" - data-l10n-id="tor-bridges-menu-item-edit-all-bridges" - ></html:panel-item> - <html:panel-item - id="tor-bridges-options-remove-all-menu-item" - data-l10n-attrs="accesskey" - 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: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:div> - <html:div - id="tor-bridges-grid-display" - class="tor-bridges-grid" - role="grid" - aria-labelledby="tor-bridges-current-heading-non-search" - hidden="hidden" - ></html:div> - <html:template id="tor-bridges-grid-row-template"> - <html:div class="tor-bridges-grid-row" role="row"> - <!-- TODO: lox status cell for new bridges? --> - <html:span - class="tor-bridges-type-cell tor-bridges-grid-cell" - role="gridcell" - ></html:span> - <html:span class="tor-bridges-emojis-block" role="none"></html:span> - <html:span class="tor-bridges-grid-end-block" role="none"> - <html:span - class="tor-bridges-address-cell tor-bridges-grid-cell" - role="gridcell" - > - <html:span class="tor-bridges-address-cell-text"></html:span> - </html:span> - <html:span - class="tor-bridges-status-cell tor-bridges-grid-cell" - role="gridcell" - > - <html:div class="bridge-status-badge"> - <html:div class="bridge-status-icon"></html:div> - <html:span class="tor-bridges-status-cell-text"></html:span> - </html:div> - </html:span> - <html:span - class="tor-bridges-options-cell tor-bridges-grid-cell" - role="gridcell" - > - <html:button - class="tor-bridges-options-cell-button tor-bridges-options-button tor-bridges-grid-focus" - aria-haspopup="menu" - aria-expanded="false" - data-l10n-id="tor-bridges-individual-bridge-options-button" - ></html:button> - <html:panel-list - class="tor-bridges-individual-options-menu" - data-hidden-from-search="true" - > - <html:panel-item - class="tor-bridges-options-qr-one-menu-item" - data-l10n-attrs="accesskey" - data-l10n-id="tor-bridges-menu-item-qr-address" - ></html:panel-item> - <html:panel-item - class="tor-bridges-options-copy-one-menu-item" - data-l10n-attrs="accesskey" - data-l10n-id="tor-bridges-menu-item-copy-address" - ></html:panel-item> - <html:panel-item - class="tor-bridges-options-remove-one-menu-item" - data-l10n-attrs="accesskey" - data-l10n-id="tor-bridges-menu-item-remove-bridge" - ></html:panel-item> - </html:panel-list> - </html:span> - </html:span> - </html:div> - </html:template> - <html:div - id="tor-bridges-share" - class="tor-bridges-details-box" - hidden="hidden" - > - <html:h3 - class="tor-bridges-share-heading tor-small-heading tor-non-search-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 - id="tor-bridges-share-description" - data-l10n-id="tor-bridges-share-description" - ></html:span> - <html:button - id="tor-bridges-copy-addresses-button" - data-l10n-id="tor-bridges-copy-addresses-button" - ></html:button> - <html:button - id="tor-bridges-qr-addresses-button" - data-l10n-id="tor-bridges-qr-addresses-button" - ></html:button> - </html:div> - <html:div id="tor-bridges-lox-status" hidden="hidden"> - <html:div data-l10n-id="tor-bridges-lox-description"></html:div> - <html:div - id="tor-bridges-lox-details" - class="tor-bridges-details-box tor-bridges-lox-box" - hidden="hidden" - > - <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" - tabindex="-1" - ></html:h4> - <html:ul class="tor-bridges-lox-list"> - <html:li - id="tor-bridges-lox-next-unlock-gain-bridges" - class="tor-bridges-lox-list-item tor-bridges-lox-list-item-bridge" - data-l10n-id="tor-bridges-lox-unlock-two-bridges" - hidden="hidden" - ></html:li> - <html:li - id="tor-bridges-lox-next-unlock-first-invites" - class="tor-bridges-lox-list-item tor-bridges-lox-list-item-invite" - data-l10n-id="tor-bridges-lox-unlock-first-invites" - hidden="hidden" - ></html:li> - <html:li - id="tor-bridges-lox-next-unlock-more-invites" - class="tor-bridges-lox-list-item tor-bridges-lox-list-item-invite" - data-l10n-id="tor-bridges-lox-unlock-more-invites" - hidden="hidden" - ></html:li> - </html:ul> - <html:div - id="tor-bridges-lox-remaining-invites" - hidden="hidden" - ></html:div> - <html:button - id="tor-bridges-lox-show-invites-button" - class="tor-bridges-lox-button" - data-l10n-id="tor-bridges-lox-show-invites-button" - hidden="hidden" - ></html:button> - </html:div> - <html:div - id="tor-bridges-lox-unlock-alert" - role="alert" - class="tor-bridges-details-box tor-bridges-lox-box" - hidden="hidden" - > - <html:img alt="" class="tor-bridges-lox-image-inner" /> - <html:img alt="" class="tor-bridges-lox-image-outer" /> - <html:div - id="tor-bridge-unlock-alert-title" - class="tor-small-heading tor-bridges-lox-intro" - ></html:div> - <html:ul class="tor-bridges-lox-list"> - <html:li - id="tor-bridges-lox-unlock-alert-gain-bridges" - class="tor-bridges-lox-list-item tor-bridges-lox-list-item-bridge" - data-l10n-id="tor-bridges-lox-gained-two-bridges" - hidden="hidden" - ></html:li> - <html:li - id="tor-bridges-lox-unlock-alert-new-bridges" - class="tor-bridges-lox-list-item tor-bridges-lox-list-item-bridge" - data-l10n-id="tor-bridges-lox-new-bridges" - hidden="hidden" - ></html:li> - <html:li - id="tor-bridges-lox-unlock-alert-invites" - class="tor-bridges-lox-list-item tor-bridges-lox-list-item-invite" - hidden="hidden" - ></html:li> - </html:ul> - <html:button - id="tor-bridges-lox-unlock-alert-button" - class="tor-bridges-lox-button" - data-l10n-id="tor-bridges-lox-got-it-button" - ></html:button> - </html:div> - </html:div> - </html:div> - <html:h2 - class="tor-bridges-change-heading tor-medium-heading tor-non-search-heading" - ></html:h2> - <!-- Add a duplicate search heading. See tor-browser#43320. - - This has the same content, but a smaller font. --> - <html:h3 - class="tor-bridges-change-heading tor-small-heading tor-search-heading" - ></html:h3> - <hbox align="center"> - <description - flex="1" - data-l10n-id="tor-bridges-select-built-in-description" - /> - <html:button - id="tor-bridges-open-built-in-dialog-button" - class="accessory-button" - data-l10n-id="tor-bridges-select-built-in-button" - ></html:button> - </hbox> - <hbox align="center"> - <description id="tor-bridges-user-provide-description" flex="1" /> - <html:button - id="tor-bridges-open-user-provide-dialog-button" - class="accessory-button" - ></html:button> - </hbox> - <html:h3 - class="tor-bridges-provider-heading tor-medium-heading tor-non-search-heading" - data-l10n-id="tor-bridges-find-more-heading" - ></html:h3> - <!-- Add a duplicate search heading. See tor-browser#43320. - - This has the same content, but a smaller font. --> - <html:h4 - class="tor-bridges-provider-heading tor-small-heading tor-search-heading" - data-l10n-id="tor-bridges-find-more-heading" - ></html:h4> - <description - data-l10n-id="tor-bridges-find-more-description" - class="description-deemphasized" - /> - <html:div id="tor-bridges-provider-area"> - <html:ul id="tor-bridges-provider-list"> - <html:li class="tor-bridges-provider-item"> - <html:img - id="tor-bridges-provider-icon-telegram" - class="tor-bridges-provider-icon" - alt="" - /> - <html:div - class="tor-bridges-provider-name" - data-l10n-id="tor-bridges-provider-telegram-name" - ></html:div> - <html:div - id="tor-bridges-provider-instruction-telegram" - class="tor-bridges-provider-instruction" - > - <html:a data-l10n-name="user"></html:a> - </html:div> - </html:li> - <html:li class="tor-bridges-provider-item"> - <html:img - id="tor-bridges-provider-icon-web" - class="tor-bridges-provider-icon" - alt="" - /> - <html:div - class="tor-bridges-provider-name" - data-l10n-id="tor-bridges-provider-web-name" - ></html:div> - <html:div - class="tor-bridges-provider-instruction" - data-l10n-id="tor-bridges-provider-web-instruction" - data-l10n-args='{ "url": "bridges.torproject.org" }' - > - <html:a - href="https://bridges.torproject.org" - data-l10n-name="url" - ></html:a> - </html:div> - </html:li> - <html:li class="tor-bridges-provider-item"> - <html:img - id="tor-bridges-provider-icon-email" - class="tor-bridges-provider-icon" - alt="" - /> - <html:div - class="tor-bridges-provider-name" - data-l10n-id="tor-bridges-provider-email-name" - ></html:div> - <html:div - class="tor-bridges-provider-instruction" - data-l10n-id="tor-bridges-provider-email-instruction" - data-l10n-args='{ "address": "bridges@torproject.org" }' - ></html:div> - </html:li> - </html:ul> - <html:div id="tor-bridges-request-box" class="tor-bridges-box"> - <html:img - alt="" - src="chrome://browser/content/torpreferences/bridge-bot.svg" - ></html:img> - <html:div - id="tor-bridges-request-description" - data-l10n-id="tor-bridges-request-from-browser" - ></html:div> - <html:button - id="tor-bridges-open-request-dialog-button" - data-l10n-id="tor-bridges-request-button" - ></html:button> - </html:div> - </html:div> - </groupbox> - - <!-- Advanced --> - <hbox - class="subcategory" - data-category="paneConnection" - hidden="true" - data-srd-groupid="connectionStatus" - > - <html:h1 - id="tor-advanced-subcategory-heading-non-search" - data-l10n-id="tor-advanced-settings-heading" - ></html:h1> - </hbox> - <groupbox - id="torPreferences-advanced-group" - data-category="paneConnection" - hidden="true" - aria-labelledby="tor-advanced-subcategory-heading-non-search" - data-srd-groupid="connectionStatus" - > - <!-- Add a search-header that only appears in search results as a substitute - - for the hidden h1 element. See tor-browser#43320. - - NOTE: Usually the first xul:label will act as the accessible name for - - a xul:groubbox element *if* it is not hidden. Since the search-header - - is sometimes hidden we need an explicit aria-labelledby anyway. - - However, we keep the wrapper xul:label for styling consistency with the - - other settings. --> - <label class="search-header" hidden="true"> - <html:h2 data-l10n-id="tor-advanced-settings-heading"></html:h2> - </label> - <hbox align="center"> - <description data-l10n-id="tor-advanced-settings-description" flex="1" /> - <html:button - id="torPreferences-advanced-button" - class="accessory-button" - data-l10n-id="tor-advanced-settings-button" - ></html:button> - </hbox> - <hbox align="center" data-subcategory="viewlogs"> - <description data-l10n-id="tor-view-log-description" flex="1" /> - <html:button - id="torPreferences-buttonTorLogs" - class="accessory-button" - data-l10n-id="tor-view-log-button" - ></html:button> - </hbox> - </groupbox> -</html:template> ===================================== browser/components/torpreferences/content/connectionPane.js deleted ===================================== @@ -1,2663 +0,0 @@ -// Copyright (c) 2022, The Tor Project, Inc. -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -"use strict"; - -/* import-globals-from /browser/components/preferences/preferences.js */ -/* import-globals-from /browser/components/preferences/search.js */ - -const { setTimeout, clearTimeout } = ChromeUtils.importESModule( - "resource://gre/modules/Timer.sys.mjs" -); - -const { TorSettings, TorSettingsTopics, TorBridgeSource } = - ChromeUtils.importESModule("moz-src:///toolkit/modules/TorSettings.sys.mjs"); - -const { TorParsers } = ChromeUtils.importESModule( - "moz-src:///toolkit/components/tor-launcher/TorParsers.sys.mjs" -); -const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule( - "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs" -); - -const { InternetStatus, TorConnect, TorConnectTopics, TorConnectStage } = - ChromeUtils.importESModule("moz-src:///toolkit/modules/TorConnect.sys.mjs"); - -const { TorConnectParent } = ChromeUtils.importESModule( - "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs" -); - -const { QRCode } = ChromeUtils.importESModule( - "moz-src:///toolkit/modules/QRCode.sys.mjs" -); - -const { TorStrings } = ChromeUtils.importESModule( - "moz-src:///toolkit/modules/TorStrings.sys.mjs" -); - -const { Lox, LoxTopics } = ChromeUtils.importESModule( - "moz-src:///toolkit/components/lox/Lox.sys.mjs" -); - -const log = console.createInstance({ - maxLogLevel: "Warn", - prefix: "connectionPane", -}); - -/* - * Fake Lox module: - -const Lox = { - levelHistory: [0, 1], - // levelHistory: [1, 2], - // levelHistory: [2, 3], - // levelHistory: [3, 4], - // levelHistory: [0, 1, 2], - // levelHistory: [1, 2, 3], - // levelHistory: [4, 3], - // levelHistory: [4, 1], - // levelHistory: [2, 1], - //levelHistory: [2, 3, 4, 1, 2], - // Gain some invites and then loose them all. Shouldn't show any change. - // levelHistory: [0, 1, 2, 1], - // levelHistory: [1, 2, 3, 1], - getEventData() { - let prevLevel = this.levelHistory[0]; - const events = []; - for (let i = 1; i < this.levelHistory.length; i++) { - const level = this.levelHistory[i]; - events.push({ type: level > prevLevel ? "levelup" : "blockage", newLevel: level }); - prevLevel = level; - } - return events; - }, - clearEventData() { - this.levelHistory = []; - }, - nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 1 }, - //nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 2 }, - //nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 3 }, - //nextUnlock: { date: "2024-01-31T00:00:00Z", nextLevel: 4 }, - getNextUnlock() { - return this.nextUnlock; - }, - remainingInvites: 3, - // remainingInvites: 0, - getRemainingInviteCount() { - return this.remainingInvites; - }, - invites: [], - // invites: ["a", "b"], - getInvites() { - return this.invites; - }, -}; -*/ - -/** - * Get the ID/fingerprint of the bridge used in the most recent Tor circuit. - * - * @returns {string?} - The bridge ID or null if a bridge with an id was not - * used in the last circuit. - */ -async function getConnectedBridgeId() { - // TODO: PieroV: We could make sure TorSettings is in sync by monitoring also - // changes of settings. At that point, we could query it, instead of doing a - // query over the control port. - let bridge = null; - try { - const provider = await TorProviderBuilder.build(); - bridge = provider.currentBridge; - } catch (e) { - console.warn("Could not get current bridge", e); - } - return bridge?.fingerprint ?? null; -} - -/** - * Show the bridge QR to the user. - * - * @param {string} bridgeString - The string to use in the QR. - */ -function showBridgeQr(bridgeString) { - gSubDialog.open( - "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml", - { features: "resizable=yes" }, - bridgeString - ); -} - -// TODO: Instead of aria-live in the DOM, use the proposed ariaNotify -// API if it gets accepted into firefox and works with screen readers. -// See https://github.com/WICG/proposals/issues/112 -/** - * Notification for screen reader users. - */ -const gBridgesNotification = { - /** - * The screen reader area that shows updates. - * - * @type {Element?} - */ - _updateArea: null, - /** - * The text for the screen reader update. - * - * @type {Element?} - */ - _textEl: null, - /** - * A timeout for hiding the update. - * - * @type {integer?} - */ - _hideUpdateTimeout: null, - - /** - * Initialize the area for notifications. - */ - init() { - this._updateArea = document.getElementById("tor-bridges-update-area"); - this._textEl = document.getElementById("tor-bridges-update-area-text"); - }, - - /** - * Post a new notification, replacing any existing one. - * - * @param {string} type - The notification type. - */ - post(type) { - this._updateArea.hidden = false; - // First we clear the update area to reset the text to be empty. - this._textEl.removeAttribute("data-l10n-id"); - this._textEl.textContent = ""; - if (this._hideUpdateTimeout !== null) { - clearTimeout(this._hideUpdateTimeout); - this._hideUpdateTimeout = null; - } - - let updateId; - switch (type) { - case "removed-one": - updateId = "tor-bridges-update-removed-one-bridge"; - break; - case "removed-all": - updateId = "tor-bridges-update-removed-all-bridges"; - break; - case "changed": - default: - // Generic message for when bridges change. - updateId = "tor-bridges-update-changed-bridges"; - break; - } - - // Hide the area after 5 minutes, when the update is not "recent" any - // more. - this._hideUpdateTimeout = setTimeout(() => { - this._updateArea.hidden = true; - }, 300000); - - // Wait a small amount of time to actually set the textContent. Otherwise - // the screen reader (tested with Orca) may not pick up on the change in - // text. - setTimeout(() => { - document.l10n.setAttributes(this._textEl, updateId); - }, 500); - }, -}; - -/** - * Controls the bridge grid. - */ -const gBridgeGrid = { - /** - * The grid element. - * - * @type {Element?} - */ - _grid: null, - /** - * The template for creating new rows. - * - * @type {HTMLTemplateElement?} - */ - _rowTemplate: null, - - /** - * @typedef {object} BridgeGridRow - * - * @property {Element} element - The row element. - * @property {Element} optionsButton - The options button. - * @property {Element} menu - The options menupopup. - * @property {Element} statusEl - The bridge status element. - * @property {Element} statusText - The status text. - * @property {string} bridgeLine - The identifying bridge string for this row. - * @property {string?} bridgeId - The ID/fingerprint for the bridge, or null - * if it doesn't have one. - * @property {integer} index - The index of the row in the grid. - * @property {boolean} connected - Whether we are connected to the bridge - * (recently in use for a Tor circuit). - * @property {BridgeGridCell[]} cells - The cells that belong to the row, - * ordered by their column. - */ - /** - * @typedef {object} BridgeGridCell - * - * @property {Element} element - The cell element. - * @property {Element} focusEl - The element belonging to the cell that should - * receive focus. Should be the cell element itself, or an interactive - * focusable child. - * @property {integer} columnIndex - The index of the column this cell belongs - * to. - * @property {BridgeGridRow} row - The row this cell belongs to. - */ - /** - * The current rows in the grid. - * - * @type {BridgeGridRow[]} - */ - _rows: [], - /** - * The cell that should be the focus target when the user moves focus into the - * grid, or null if the grid itself should be the target. - * - * @type {BridgeGridCell?} - */ - _focusCell: null, - - /** - * Initialize the bridge grid. - */ - init() { - this._grid = document.getElementById("tor-bridges-grid-display"); - // Initially, make only the grid itself part of the keyboard tab cycle. - // matches _focusCell = null. - this._grid.tabIndex = 0; - - this._rowTemplate = document.getElementById( - "tor-bridges-grid-row-template" - ); - - this._grid.addEventListener("keydown", this); - this._grid.addEventListener("mousedown", this); - this._grid.addEventListener("focusin", this); - - Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged); - - // NOTE: Before initializedPromise completes, this area is hidden. - TorSettings.initializedPromise.then(() => { - this._updateRows(true); - }); - }, - - /** - * Uninitialize the bridge grid. - */ - uninit() { - Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged); - this.deactivate(); - }, - - /** - * Whether the grid is visible and responsive. - * - * @type {boolean} - */ - _active: false, - - /** - * Activate and show the bridge grid. - */ - activate() { - if (this._active) { - return; - } - - this._active = true; - - Services.obs.addObserver(this, TorProviderTopics.BridgeChanged); - - this._grid.hidden = false; - - this._updateConnectedBridge(); - }, - - /** - * Deactivate and hide the bridge grid. - */ - deactivate() { - if (!this._active) { - return; - } - - this._active = false; - - this._forceCloseRowMenus(); - - this._grid.hidden = true; - - Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged); - }, - - observe(subject, topic) { - switch (topic) { - case TorSettingsTopics.SettingsChanged: { - const { changes } = subject.wrappedJSObject; - if ( - changes.includes("bridges.source") || - changes.includes("bridges.bridge_strings") - ) { - this._updateRows(); - } - break; - } - case TorProviderTopics.BridgeChanged: - this._updateConnectedBridge(); - break; - } - }, - - handleEvent(event) { - if (event.type === "keydown") { - if (event.altKey || event.shiftKey || event.metaKey || event.ctrlKey) { - // Don't interfere with these events. - return; - } - - if (this._rows.some(row => row.menu.open)) { - // Have an open menu, let the menu handle the event instead. - return; - } - - let numRows = this._rows.length; - if (!numRows) { - // Nowhere for focus to go. - return; - } - - let moveRow = 0; - let moveColumn = 0; - const isLTR = this._grid.matches(":dir(ltr)"); - switch (event.key) { - case "ArrowDown": - moveRow = 1; - break; - case "ArrowUp": - moveRow = -1; - break; - case "ArrowRight": - moveColumn = isLTR ? 1 : -1; - break; - case "ArrowLeft": - moveColumn = isLTR ? -1 : 1; - break; - default: - return; - } - - // Prevent scrolling the nearest scroll container. - event.preventDefault(); - - const curCell = this._focusCell; - let row = curCell ? curCell.row.index + moveRow : 0; - let column = curCell ? curCell.columnIndex + moveColumn : 0; - - // Clamp in bounds. - if (row < 0) { - row = 0; - } else if (row >= numRows) { - row = numRows - 1; - } - - const numCells = this._rows[row].cells.length; - if (column < 0) { - column = 0; - } else if (column >= numCells) { - column = numCells - 1; - } - - const newCell = this._rows[row].cells[column]; - - if (newCell !== curCell) { - this._setFocus(newCell); - } - } else if (event.type === "mousedown") { - if (event.button !== 0) { - return; - } - // Move focus index to the clicked target. - // NOTE: Since the cells and the grid have "tabindex=-1", they are still - // click-focusable. Therefore, the default mousedown handler will try to - // move focus to it. - // Rather than block this default handler, we instead re-direct the focus - // to the correct cell in the "focusin" listener. - const newCell = this._getCellFromTarget(event.target); - // NOTE: If newCell is null, then we do nothing here, but instead wait for - // the focusin handler to trigger. - if (newCell && newCell !== this._focusCell) { - this._setFocus(newCell); - } - } else if (event.type === "focusin") { - const focusCell = this._getCellFromTarget(event.target); - if (focusCell !== this._focusCell) { - // Focus is not where it is expected. - // E.g. the user has clicked the edge of the grid. - // Restore focus immediately back to the cell we expect. - this._setFocus(this._focusCell); - } - } - }, - - /** - * Return the cell that was the target of an event. - * - * @param {Element} element - The target of an event. - * - * @returns {BridgeGridCell?} - The cell that the element belongs to, or null - * if it doesn't belong to any cell. - */ - _getCellFromTarget(element) { - for (const row of this._rows) { - for (const cell of row.cells) { - if (cell.element.contains(element)) { - return cell; - } - } - } - return null; - }, - - /** - * Determine whether the document's active element (focus) is within the grid - * or not. - * - * @returns {boolean} - Whether focus is within this grid or not. - */ - _focusWithin() { - return this._grid.contains(document.activeElement); - }, - - /** - * Set the cell that should be the focus target of the grid, possibly moving - * the document's focus as well. - * - * @param {BridgeGridCell?} cell - The cell to make the focus target, or null - * if the grid itself should be the target. - * @param {boolean} [focusWithin] - Whether focus should be moved within the - * grid. If undefined, this will move focus if the grid currently contains - * the document's focus. - */ - _setFocus(cell, focusWithin) { - if (focusWithin === undefined) { - focusWithin = this._focusWithin(); - } - const prevFocusElement = this._focusCell - ? this._focusCell.focusEl - : this._grid; - const newFocusElement = cell ? cell.focusEl : this._grid; - - if (prevFocusElement !== newFocusElement) { - prevFocusElement.tabIndex = -1; - newFocusElement.tabIndex = 0; - } - // Set _focusCell now, before we potentially call "focus", which can trigger - // the "focusin" handler. - this._focusCell = cell; - - if (focusWithin) { - // Focus was within the grid, so we need to actively move it to the new - // element. - newFocusElement.focus({ preventScroll: true }); - // Scroll to the whole cell into view, rather than just the focus element. - (cell?.element ?? newFocusElement).scrollIntoView({ - block: "nearest", - inline: "nearest", - }); - } - }, - - /** - * Reset the grids focus to be the first row's first cell, if any. - * - * @param {boolean} [focusWithin] - Whether focus should be moved within the - * grid. If undefined, this will move focus if the grid currently contains - * the document's focus. - */ - _resetFocus(focusWithin) { - this._setFocus( - this._rows.length ? this._rows[0].cells[0] : null, - focusWithin - ); - }, - - /** - * The bridge ID/fingerprint of the most recently used bridge (appearing in - * the latest Tor circuit). Roughly corresponds to the bridge we are currently - * connected to. - * - * null if there are no such bridges. - * - * @type {string?} - */ - _connectedBridgeId: null, - /** - * Update _connectedBridgeId. - */ - async _updateConnectedBridge() { - const bridgeId = await getConnectedBridgeId(); - if (bridgeId === this._connectedBridgeId) { - return; - } - this._connectedBridgeId = bridgeId; - for (const row of this._rows) { - this._updateRowStatus(row); - } - }, - - /** - * Update the status of a row. - * - * @param {BridgeGridRow} row - The row to update. - */ - _updateRowStatus(row) { - const connected = row.bridgeId && this._connectedBridgeId === row.bridgeId; - // NOTE: row.connected is initially undefined, so won't match `connected`. - if (connected === row.connected) { - return; - } - - row.connected = connected; - - const noStatus = !connected; - - row.element.classList.toggle("hide-status", noStatus); - row.statusEl.classList.toggle("bridge-status-none", noStatus); - row.statusEl.classList.toggle("bridge-status-connected", connected); - - if (connected) { - document.l10n.setAttributes( - row.statusText, - "tor-bridges-status-connected" - ); - } else { - document.l10n.setAttributes(row.statusText, "tor-bridges-status-none"); - } - }, - - /** - * Create a new row for the grid. - * - * @param {string} bridgeLine - The bridge line for this row, which also acts - * as its ID. - * - * @returns {BridgeGridRow} - A new row, with then "index" unset and the - * "element" without a parent. - */ - _createRow(bridgeLine) { - let details; - try { - details = TorParsers.parseBridgeLine(bridgeLine); - } catch (e) { - console.error(`Detected invalid bridge line: ${bridgeLine}`, e); - } - const row = { - element: this._rowTemplate.content.children[0].cloneNode(true), - bridgeLine, - bridgeId: details?.id ?? null, - cells: [], - }; - - const emojiBlock = row.element.querySelector(".tor-bridges-emojis-block"); - const BridgeEmoji = customElements.get("tor-bridge-emoji"); - for (const cell of BridgeEmoji.createForAddress(bridgeLine)) { - // Each emoji is its own cell, we rely on the fact that createForAddress - // always returns four elements. - cell.setAttribute("role", "gridcell"); - cell.classList.add("tor-bridges-grid-cell", "tor-bridges-emoji-cell"); - emojiBlock.append(cell); - } - - for (const [columnIndex, element] of row.element - .querySelectorAll(".tor-bridges-grid-cell") - .entries()) { - const focusEl = - element.querySelector(".tor-bridges-grid-focus") ?? element; - // Set a negative tabIndex, this makes the element click-focusable but not - // part of the tab navigation sequence. - focusEl.tabIndex = -1; - row.cells.push({ element, focusEl, columnIndex, row }); - } - - const transport = details?.transport ?? "vanilla"; - const typeCell = row.element.querySelector(".tor-bridges-type-cell"); - if (transport === "vanilla") { - document.l10n.setAttributes(typeCell, "tor-bridges-type-prefix-generic"); - } else { - document.l10n.setAttributes(typeCell, "tor-bridges-type-prefix", { - type: transport, - }); - } - - row.element.querySelector(".tor-bridges-address-cell-text").textContent = - bridgeLine; - - row.statusEl = row.element.querySelector( - ".tor-bridges-status-cell .bridge-status-badge" - ); - row.statusText = row.element.querySelector(".tor-bridges-status-cell-text"); - - this._initRowMenu(row); - - this._updateRowStatus(row); - return row; - }, - - /** - * The row menu index used for generating new ids. - * - * @type {integer} - */ - _rowMenuIndex: 0, - /** - * Generate a new id for the options menu. - * - * @returns {string} - The new id. - */ - _generateRowMenuId() { - const id = `tor-bridges-individual-options-menu-${this._rowMenuIndex}`; - // Assume we won't run out of ids. - this._rowMenuIndex++; - return id; - }, - - /** - * Initialize the shared menu for a row. - * - * @param {BridgeGridRow} row - The row to initialize the menu of. - */ - _initRowMenu(row) { - row.menu = row.element.querySelector( - ".tor-bridges-individual-options-menu" - ); - row.optionsButton = row.element.querySelector( - ".tor-bridges-options-cell-button" - ); - - row.menu.id = this._generateRowMenuId(); - row.optionsButton.setAttribute("aria-controls", row.menu.id); - - row.optionsButton.addEventListener("click", event => { - row.menu.toggle(event); - }); - - row.menu.addEventListener("hidden", () => { - // Make sure the button receives focus again when the menu is hidden. - // Currently, panel-list.js only does this when the menu is opened with a - // keyboard, but this causes focus to be lost from the page if the user - // uses a mixture of keyboard and mouse. - row.optionsButton.focus(); - }); - - const qrItem = row.menu.querySelector( - ".tor-bridges-options-qr-one-menu-item" - ); - const removeItem = row.menu.querySelector( - ".tor-bridges-options-remove-one-menu-item" - ); - row.menu.addEventListener("showing", () => { - const show = - this._bridgeSource === TorBridgeSource.UserProvided || - this._bridgeSource === TorBridgeSource.BridgeDB; - qrItem.hidden = !show; - removeItem.hidden = !show; - }); - - qrItem.addEventListener("click", () => { - const bridgeLine = row.bridgeLine; - if (!bridgeLine) { - return; - } - showBridgeQr(bridgeLine); - }); - row.menu - .querySelector(".tor-bridges-options-copy-one-menu-item") - .addEventListener("click", () => { - const clipboard = Cc[ - "@mozilla.org/widget/clipboardhelper;1" - ].getService(Ci.nsIClipboardHelper); - clipboard.copyString(row.bridgeLine); - }); - removeItem.addEventListener("click", () => { - const bridgeLine = row.bridgeLine; - const source = TorSettings.bridges.source; - const strings = TorSettings.bridges.bridge_strings; - const index = strings.indexOf(bridgeLine); - if (index === -1) { - return; - } - strings.splice(index, 1); - - if (strings.length) { - TorSettings.changeSettings({ - bridges: { source, bridge_strings: strings }, - }); - } else { - // Remove all bridges and disable. - TorSettings.changeSettings({ - bridges: { source: TorBridgeSource.Invalid }, - }); - } - }); - }, - - /** - * Force the row menu to close. - */ - _forceCloseRowMenus() { - for (const row of this._rows) { - row.menu.hide(null, { force: true }); - } - }, - - /** - * The known bridge source. - * - * Initially null to indicate that it is unset. - * - * @type {integer?} - */ - _bridgeSource: null, - /** - * The bridge sources this is shown for. - * - * @type {string[]} - */ - _supportedSources: [ - TorBridgeSource.BridgeDB, - TorBridgeSource.UserProvided, - TorBridgeSource.Lox, - ], - - /** - * Update the grid to show the latest bridge strings. - * - * @param {boolean} [initializing=false] - Whether this is being called as - * part of initialization. - */ - _updateRows(initializing = false) { - // Store whether we have focus within the grid, before removing or hiding - // DOM elements. - const focusWithin = this._focusWithin(); - - let lostAllBridges = false; - let newSource = false; - const bridgeSource = TorSettings.bridges.source; - if (bridgeSource !== this._bridgeSource) { - newSource = true; - - this._bridgeSource = bridgeSource; - - if (this._supportedSources.includes(bridgeSource)) { - this.activate(); - } else { - if (this._active && bridgeSource === TorBridgeSource.Invalid) { - lostAllBridges = true; - } - this.deactivate(); - } - } - - const ordered = this._active - ? TorSettings.bridges.bridge_strings.map(bridgeLine => { - const row = this._rows.find(r => r.bridgeLine === bridgeLine); - if (row) { - return row; - } - return this._createRow(bridgeLine); - }) - : []; - - // Whether we should reset the grid's focus. - // We always reset when we have a new bridge source. - // We reset the focus if no current Cell has focus. I.e. when adding a row - // to an empty grid, we want the focus to move to the first item. - // We also reset the focus if the current Cell is in a row that will be - // removed (including if all rows are removed). - // NOTE: In principle, if a row is removed, we could move the focus to the - // next or previous row (in the same cell column). However, most likely if - // the grid has the user focus, they are removing a single row using its - // options button. In this case, returning the user to some other row's - // options button might be more disorienting since it would not be simple - // for them to know *which* bridge they have landed on. - // NOTE: We do not reset the focus in other cases because we do not want the - // user to loose their place in the grid unnecessarily. - let resetFocus = - newSource || !this._focusCell || !ordered.includes(this._focusCell.row); - - // Remove rows no longer needed from the DOM. - let numRowsRemoved = 0; - let rowAddedOrMoved = false; - - for (const row of this._rows) { - if (!ordered.includes(row)) { - numRowsRemoved++; - // If the row menu was open, it will also be deleted. - // NOTE: Since the row menu is part of the row, focusWithin will be true - // if the menu had focus, so focus should be re-assigned. - row.element.remove(); - } - } - - // Go through all the rows to set their ".index" property and to ensure they - // are in the correct position in the DOM. - // NOTE: We could use replaceChildren to get the correct DOM structure, but - // we want to avoid rebuilding the entire tree when a single row is added or - // removed. - for (const [index, row] of ordered.entries()) { - row.index = index; - const element = row.element; - // Get the expected previous element, that should already be in the DOM - // from the previous loop. - const prevEl = index ? ordered[index - 1].element : null; - - if ( - element.parentElement === this._grid && - prevEl === element.previousElementSibling - ) { - // Already in the correct position in the DOM. - continue; - } - - rowAddedOrMoved = true; - // NOTE: Any elements already in the DOM, but not in the correct position - // will be removed and re-added by the below command. - // NOTE: if the row has document focus, then it should remain there. - if (prevEl) { - prevEl.after(element); - } else { - this._grid.prepend(element); - } - } - this._rows = ordered; - - // Restore any lost focus. - if (resetFocus) { - // If we are not active (and therefore hidden), we will not try and move - // focus (activeElement), but may still change the *focusable* element for - // when we are shown again. - this._resetFocus(this._active && focusWithin); - } - if (!this._active && focusWithin) { - // Move focus out of this element, which has been hidden. - gBridgeSettings.takeFocus(); - } - - // Notify the user if there was some change to the DOM. - // If we are initializing, we generate no notification since there has been - // no change in the setting. - if (!initializing) { - let notificationType; - if (lostAllBridges) { - // Just lost all bridges, and became de-active. - notificationType = "removed-all"; - } else if (this._rows.length) { - // Otherwise, only generate a notification if we are still active, with - // at least one bridge. - // I.e. do not generate a message if the new source is "builtin". - if (newSource) { - // A change in source. - notificationType = "changed"; - } else if (numRowsRemoved === 1 && !rowAddedOrMoved) { - // Only one bridge was removed. This is most likely in response to them - // manually removing a single bridge or using the bridge row's options - // menu. - notificationType = "removed-one"; - } else if (numRowsRemoved || rowAddedOrMoved) { - // Some other change. This is most likely in response to a manual edit - // of the existing bridges. - notificationType = "changed"; - } - // Else, there was no change. - } - - if (notificationType) { - gBridgesNotification.post(notificationType); - } - } - }, -}; - -/** - * Controls the built-in bridges area. - */ -const gBuiltinBridgesArea = { - /** - * The display area. - * - * @type {Element?} - */ - _area: null, - /** - * The type name element. - * - * @type {Element?} - */ - _nameEl: null, - /** - * The bridge type description element. - * - * @type {Element?} - */ - _descriptionEl: null, - /** - * The connection status. - * - * @type {Element?} - */ - _connectionStatusEl: null, - - /** - * Initialize the built-in bridges area. - */ - init() { - this._area = document.getElementById("tor-bridges-built-in-display"); - this._nameEl = document.getElementById("tor-bridges-built-in-type-name"); - this._descriptionEl = document.getElementById( - "tor-bridges-built-in-description" - ); - this._connectionStatusEl = document.getElementById( - "tor-bridges-built-in-connected" - ); - - Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged); - - // NOTE: Before initializedPromise completes, this area is hidden. - TorSettings.initializedPromise.then(() => { - this._updateBridgeType(true); - }); - }, - - /** - * Uninitialize the built-in bridges area. - */ - uninit() { - Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged); - this.deactivate(); - }, - - /** - * Whether the built-in area is visible and responsive. - * - * @type {boolean} - */ - _active: false, - - /** - * Activate and show the built-in bridge area. - */ - activate() { - if (this._active) { - return; - } - this._active = true; - - Services.obs.addObserver(this, TorProviderTopics.BridgeChanged); - - this._area.hidden = false; - - this._updateBridgeIds(); - this._updateConnectedBridge(); - }, - - /** - * Deactivate and hide built-in bridge area. - */ - deactivate() { - if (!this._active) { - return; - } - this._active = false; - - this._area.hidden = true; - - Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged); - }, - - observe(subject, topic) { - switch (topic) { - case TorSettingsTopics.SettingsChanged: { - const { changes } = subject.wrappedJSObject; - if ( - changes.includes("bridges.source") || - changes.includes("bridges.builtin_type") - ) { - this._updateBridgeType(); - } - if (changes.includes("bridges.bridge_strings")) { - this._updateBridgeIds(); - } - break; - } - case TorProviderTopics.BridgeChanged: - this._updateConnectedBridge(); - break; - } - }, - - /** - * Updates the shown connected state. - */ - _updateConnectedState() { - this._connectionStatusEl.classList.toggle( - "bridge-status-connected", - this._bridgeType && - this._connectedBridgeId && - this._bridgeIds.includes(this._connectedBridgeId) - ); - }, - - /** - * The currently shown bridge type. Empty if deactivated, and null if - * uninitialized. - * - * @type {string?} - */ - _bridgeType: null, - /** - * The strings for each known bridge type. - * - * @type {{[key: string]: {[key: string]: string}}} - */ - _bridgeTypeStrings: { - obfs4: { - name: "tor-bridges-built-in-obfs4-name", - description: "tor-bridges-built-in-obfs4-description", - }, - snowflake: { - name: "tor-bridges-built-in-snowflake-name", - description: "tor-bridges-built-in-snowflake-description", - }, - meek: { - name: "tor-bridges-built-in-meek-name", - description: "tor-bridges-built-in-meek-description", - }, - }, - - /** - * The known bridge source. - * - * Initially null to indicate that it is unset. - * - * @type {integer?} - */ - _bridgeSource: null, - - /** - * Update the shown bridge type. - * - * @param {boolean} [initializing=false] - Whether this is being called as - * part of initialization. - */ - async _updateBridgeType(initializing = false) { - let lostAllBridges = false; - let newSource = false; - const bridgeSource = TorSettings.bridges.source; - if (bridgeSource !== this._bridgeSource) { - newSource = true; - - this._bridgeSource = bridgeSource; - - if (bridgeSource === TorBridgeSource.BuiltIn) { - this.activate(); - } else { - if (this._active && bridgeSource === TorBridgeSource.Invalid) { - lostAllBridges = true; - } - const hadFocus = this._area.contains(document.activeElement); - this.deactivate(); - if (hadFocus) { - gBridgeSettings.takeFocus(); - } - } - } - - const bridgeType = this._active ? TorSettings.bridges.builtin_type : ""; - - let newType = false; - if (bridgeType !== this._bridgeType) { - newType = true; - - this._bridgeType = bridgeType; - - const bridgeStrings = this._bridgeTypeStrings[bridgeType]; - if (bridgeStrings) { - document.l10n.setAttributes(this._nameEl, bridgeStrings.name); - document.l10n.setAttributes( - this._descriptionEl, - bridgeStrings.description - ); - } else { - // Unknown type, or no type. - this._nameEl.removeAttribute("data-l10n-id"); - this._nameEl.textContent = bridgeType; - this._descriptionEl.removeAttribute("data-l10n-id"); - this._descriptionEl.textContent = ""; - } - - this._updateConnectedState(); - } - - // Notify the user if there was some change to the type. - // If we are initializing, we generate no notification since there has been - // no change in the setting. - if (!initializing) { - let notificationType; - if (lostAllBridges) { - // Just lost all bridges, and became de-active. - notificationType = "removed-all"; - } else if (this._active && (newSource || newType)) { - // Otherwise, only generate a notification if we are still active, with - // a bridge type. - // I.e. do not generate a message if the new source is not "builtin". - notificationType = "changed"; - } - - if (notificationType) { - gBridgesNotification.post(notificationType); - } - } - }, - - /** - * The bridge IDs/fingerprints for the built-in bridges. - * - * @type {Array<string>} - */ - _bridgeIds: [], - /** - * Update _bridgeIds - */ - _updateBridgeIds() { - this._bridgeIds = []; - for (const bridgeLine of TorSettings.bridges.bridge_strings) { - try { - this._bridgeIds.push(TorParsers.parseBridgeLine(bridgeLine).id); - } catch (e) { - console.error(`Detected invalid bridge line: ${bridgeLine}`, e); - } - } - - this._updateConnectedState(); - }, - - /** - * The bridge ID/fingerprint of the most recently used bridge (appearing in - * the latest Tor circuit). Roughly corresponds to the bridge we are currently - * connected to. - * - * @type {string?} - */ - _connectedBridgeId: null, - /** - * Update _connectedBridgeId. - */ - async _updateConnectedBridge() { - this._connectedBridgeId = await getConnectedBridgeId(); - this._updateConnectedState(); - }, -}; - -/** - * Controls the bridge pass area. - */ -const gLoxStatus = { - /** - * The status area. - * - * @type {Element?} - */ - _area: null, - /** - * The area for showing the next unlock and invites. - * - * @type {Element?} - */ - _detailsArea: null, - /** - * The list items showing the next unlocks. - * - * @type {?{[key: string]: Element}} - */ - _nextUnlockItems: null, - /** - * The day counter headings for the next unlock. - * - * One heading is shown during a search, the other is shown otherwise. - * - * @type {?Element[]} - */ - _nextUnlockCounterEls: null, - /** - * Shows the number of remaining invites. - * - * @type {Element?} - */ - _remainingInvitesEl: null, - /** - * The button to show the invites. - * - * @type {Element?} - */ - _invitesButton: null, - /** - * The alert for new unlocks. - * - * @type {Element?} - */ - _unlockAlert: null, - /** - * The list items showing the unlocks. - * - * @type {?{[key: string]: Element}} - */ - _unlockItems: null, - /** - * The alert title. - * - * @type {Element?} - */ - _unlockAlertTitle: null, - /** - * The alert invites item. - * - * @type {Element?} - */ - _unlockAlertInvitesItem: null, - /** - * Button for the user to dismiss the alert. - * - * @type {Element?} - */ - _unlockAlertButton: null, - - /** - * Initialize the bridge pass area. - */ - init() { - if (!Lox.enabled) { - // Area should remain inactive and hidden. - return; - } - - this._area = document.getElementById("tor-bridges-lox-status"); - this._detailsArea = document.getElementById("tor-bridges-lox-details"); - this._nextUnlockItems = { - gainBridges: document.getElementById( - "tor-bridges-lox-next-unlock-gain-bridges" - ), - firstInvites: document.getElementById( - "tor-bridges-lox-next-unlock-first-invites" - ), - moreInvites: document.getElementById( - "tor-bridges-lox-next-unlock-more-invites" - ), - }; - this._nextUnlockCounterEls = Array.from( - document.querySelectorAll(".tor-bridges-lox-next-unlock-counter") - ); - this._remainingInvitesEl = document.getElementById( - "tor-bridges-lox-remaining-invites" - ); - this._invitesButton = document.getElementById( - "tor-bridges-lox-show-invites-button" - ); - this._unlockAlert = document.getElementById("tor-bridges-lox-unlock-alert"); - this._unlockItems = { - gainBridges: document.getElementById( - "tor-bridges-lox-unlock-alert-gain-bridges" - ), - newBridges: document.getElementById( - "tor-bridges-lox-unlock-alert-new-bridges" - ), - invites: document.getElementById("tor-bridges-lox-unlock-alert-invites"), - }; - this._unlockAlertTitle = document.getElementById( - "tor-bridge-unlock-alert-title" - ); - this._unlockAlertInviteItem = document.getElementById( - "tor-bridges-lox-unlock-alert-invites" - ); - this._unlockAlertButton = document.getElementById( - "tor-bridges-lox-unlock-alert-button" - ); - - this._invitesButton.addEventListener("click", () => { - gSubDialog.open( - "chrome://browser/content/torpreferences/loxInviteDialog.xhtml", - { features: "resizable=yes" } - ); - }); - this._unlockAlertButton.addEventListener("click", () => { - Lox.clearEventData(this._loxId); - }); - - Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged); - Services.obs.addObserver(this, LoxTopics.UpdateActiveLoxId); - Services.obs.addObserver(this, LoxTopics.UpdateEvents); - Services.obs.addObserver(this, LoxTopics.UpdateNextUnlock); - Services.obs.addObserver(this, LoxTopics.UpdateRemainingInvites); - Services.obs.addObserver(this, LoxTopics.NewInvite); - - // NOTE: Before initializedPromise completes, this area is hidden. - TorSettings.initializedPromise.then(() => { - this._updateLoxId(); - }); - }, - - /** - * Uninitialize the built-in bridges area. - */ - uninit() { - if (!Lox.enabled) { - return; - } - - Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged); - Services.obs.removeObserver(this, LoxTopics.UpdateActiveLoxId); - Services.obs.removeObserver(this, LoxTopics.UpdateEvents); - Services.obs.removeObserver(this, LoxTopics.UpdateNextUnlock); - Services.obs.removeObserver(this, LoxTopics.UpdateRemainingInvites); - Services.obs.removeObserver(this, LoxTopics.NewInvite); - }, - - observe(subject, topic) { - switch (topic) { - case TorSettingsTopics.SettingsChanged: { - const { changes } = subject.wrappedJSObject; - if (changes.includes("bridges.source")) { - this._updateLoxId(); - } - // NOTE: We do not call _updateLoxId when "bridges.lox_id" is in the - // changes. Instead we wait until 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. - break; - } - case LoxTopics.UpdateActiveLoxId: - this._updateLoxId(); - break; - case LoxTopics.UpdateNextUnlock: - this._updateNextUnlock(); - break; - case LoxTopics.UpdateEvents: - this._updatePendingEvents(); - break; - case LoxTopics.UpdateRemainingInvites: - this._updateRemainingInvites(); - break; - case LoxTopics.NewInvite: - this._updateHaveExistingInvites(); - break; - } - }, - - /** - * The Lox id currently shown. Empty if deactivated, and null if - * uninitialized. - * - * @type {string?} - */ - _loxId: null, - - /** - * Update the shown bridge pass. - */ - async _updateLoxId() { - let loxId = - TorSettings.bridges.source === TorBridgeSource.Lox ? Lox.activeLoxId : ""; - if (loxId === this._loxId) { - return; - } - this._loxId = loxId; - this._area.hidden = !loxId; - // We unset _nextUnlock to ensure the areas no longer use the old value for - // the new loxId. - this._updateNextUnlock(true); - this._updateRemainingInvites(); - this._updateHaveExistingInvites(); - this._updatePendingEvents(); - }, - - /** - * The remaining invites shown, or null if uninitialized or no loxId. - * - * @type {integer?} - */ - _remainingInvites: null, - /** - * Update the shown value. - */ - _updateRemainingInvites() { - const numInvites = this._loxId - ? Lox.getRemainingInviteCount(this._loxId) - : null; - if (numInvites === this._remainingInvites) { - return; - } - this._remainingInvites = numInvites; - this._updateUnlockArea(); - this._updateInvitesArea(); - }, - /** - * Whether we have existing invites, or null if uninitialized or no loxId. - * - * @type {boolean?} - */ - _haveExistingInvites: null, - /** - * Update the shown value. - */ - _updateHaveExistingInvites() { - const haveInvites = this._loxId ? !!Lox.getInvites().length : null; - if (haveInvites === this._haveExistingInvites) { - return; - } - this._haveExistingInvites = haveInvites; - this._updateInvitesArea(); - }, - /** - * Details about the next unlock, or null if uninitialized or no loxId. - * - * @type {UnlockData?} - */ - _nextUnlock: null, - /** - * Tracker id to ensure that the results from later calls to _updateNextUnlock - * take priority over earlier calls. - * - * @type {integer} - */ - _nextUnlockCallId: 0, - /** - * Update the shown value asynchronously. - * - * @param {boolean} [unset=false] - Whether to set the _nextUnlock value to - * null before waiting for the new value. I.e. ensure that the current value - * will not be used. - */ - async _updateNextUnlock(unset = false) { - // NOTE: We do not expect the integer to exceed the maximum integer. - this._nextUnlockCallId++; - const callId = this._nextUnlockCallId; - if (unset) { - this._nextUnlock = null; - } - const nextUnlock = this._loxId - ? await Lox.getNextUnlock(this._loxId) - : null; - if (callId !== this._nextUnlockCallId) { - // Replaced by another update. - // E.g. if the _loxId changed. Or if getNextUnlock triggered - // LoxTopics.UpdateNextUnlock. - return; - } - // Should be safe to trigger the update, even when the value hasn't changed. - this._nextUnlock = nextUnlock; - this._updateUnlockArea(); - }, - /** - * The list of events the user has not yet cleared, or null if uninitialized - * or no loxId. - * - * @type {EventData[]?} - */ - _pendingEvents: null, - /** - * Update the shown value. - */ - _updatePendingEvents() { - // Should be safe to trigger the update, even when the value hasn't changed. - this._pendingEvents = this._loxId ? Lox.getEventData(this._loxId) : null; - this._updateUnlockArea(); - }, - - /** - * Update the display of the current or next unlock. - */ - _updateUnlockArea() { - if ( - !this._loxId || - this._pendingEvents === null || - this._remainingInvites === null || - this._nextUnlock === null - ) { - // Uninitialized or no Lox source. - // NOTE: This area may already be hidden by the change in Lox source, - // but we clean up for the next non-empty id. - this._unlockAlert.hidden = true; - this._detailsArea.hidden = true; - return; - } - - // Grab focus state before changing visibility. - const alertHadFocus = this._unlockAlert.contains(document.activeElement); - const detailsHadFocus = this._detailsArea.contains(document.activeElement); - - const pendingEvents = this._pendingEvents; - const showAlert = !!pendingEvents.length; - this._unlockAlert.hidden = !showAlert; - this._detailsArea.hidden = showAlert; - - if (showAlert) { - // At level 0 and level 1, we do not have any invites. - // If the user starts and ends on level 0 or 1, then overall they would - // have had no change in their invites. So we do not want to show their - // latest updates. - // NOTE: If the user starts at level > 1 and ends with level 1 (levelling - // down to level 0 should not be possible), then we *do* want to show the - // user that they now have "0" invites. - // NOTE: pendingEvents are time-ordered, with the most recent event - // *last*. - const firstEvent = pendingEvents[0]; - // NOTE: We cannot get a blockage event when the user starts at level 1 or - // 0. - const startingAtLowLevel = - firstEvent.type === "levelup" && firstEvent.newLevel <= 2; - const lastEvent = pendingEvents[pendingEvents.length - 1]; - const endingAtLowLevel = lastEvent.newLevel <= 1; - - const showInvites = !(startingAtLowLevel && endingAtLowLevel); - - let blockage = false; - let levelUp = false; - let bridgeGain = false; - // Go through events, in the order that they occurred. - for (const loxEvent of pendingEvents) { - if (loxEvent.type === "levelup") { - levelUp = true; - if (loxEvent.newLevel === 1) { - // Gain 2 bridges from level 0 to 1. - bridgeGain = true; - } - } else { - blockage = true; - } - } - - let alertTitleId; - if (levelUp && !blockage) { - alertTitleId = "tor-bridges-lox-upgrade"; - } else { - // Show as blocked bridges replaced. - // Even if we have a mixture of level ups as well. - alertTitleId = "tor-bridges-lox-blocked"; - } - document.l10n.setAttributes(this._unlockAlertTitle, alertTitleId); - document.l10n.setAttributes( - this._unlockAlertInviteItem, - "tor-bridges-lox-new-invites", - { numInvites: this._remainingInvites } - ); - this._unlockAlert.classList.toggle( - "lox-unlock-upgrade", - levelUp && !blockage - ); - this._unlockItems.gainBridges.hidden = !bridgeGain; - this._unlockItems.newBridges.hidden = !blockage; - this._unlockItems.invites.hidden = !showInvites; - } else { - // Show next unlock. - // Number of days until the next unlock, rounded up. - const numDays = Math.max( - 1, - Math.ceil( - (new Date(this._nextUnlock.date).getTime() - Date.now()) / - (24 * 60 * 60 * 1000) - ) - ); - for (const counterEl of this._nextUnlockCounterEls) { - document.l10n.setAttributes( - counterEl, - "tor-bridges-lox-days-until-unlock", - { numDays } - ); - } - - // Gain 2 bridges from level 0 to 1. After that gain invites. - this._nextUnlockItems.gainBridges.hidden = - this._nextUnlock.nextLevel !== 1; - this._nextUnlockItems.firstInvites.hidden = - this._nextUnlock.nextLevel !== 2; - this._nextUnlockItems.moreInvites.hidden = - this._nextUnlock.nextLevel <= 2; - } - - if (alertHadFocus && !showAlert) { - // Alert has become hidden, move focus back up to the now revealed details - // area. - // NOTE: We have two headings: one shown during a search and one shown - // otherwise. We focus the heading that is currently visible. - // See tor-browser#43320. - // 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 ( - this._nextUnlockCounterEls[0].checkVisibility({ - visibilityProperty: true, - }) - ) { - this._nextUnlockCounterEls[0].focus(); - } else { - this._nextUnlockCounterEls[1].focus(); - } - } else if (detailsHadFocus && showAlert) { - this._unlockAlertButton.focus(); - } - }, - - /** - * Update the invites area. - */ - _updateInvitesArea() { - let hasInvites; - if ( - !this._loxId || - this._remainingInvites === null || - this._haveExistingInvites === null - ) { - // Not initialized yet. - hasInvites = false; - } else { - hasInvites = this._haveExistingInvites || !!this._remainingInvites; - } - - if ( - !hasInvites && - (this._remainingInvitesEl.contains(document.activeElement) || - this._invitesButton.contains(document.activeElement)) - ) { - // About to loose focus. - // Unexpected for the lox level to loose all invites. - // Move to the top of the details area, which should be visible if we - // just had focus. - this._nextUnlockCounterEl.focus(); - } - // Hide the invite elements if we have no historic invites or a way of - // creating new ones. - this._remainingInvitesEl.hidden = !hasInvites; - this._invitesButton.hidden = !hasInvites; - - if (hasInvites) { - document.l10n.setAttributes( - this._remainingInvitesEl, - "tor-bridges-lox-remaining-invites", - { numInvites: this._remainingInvites } - ); - } - }, -}; - -/** - * Controls the bridge settings. - */ -const gBridgeSettings = { - /** - * The preferences <groupbox> for bridges - * - * @type {Element?} - */ - _groupEl: null, - /** - * The button for controlling whether bridges are enabled. - * - * @type {Element?} - */ - _toggleButton: null, - /** - * The area for showing current bridges. - * - * @type {Element?} - */ - _bridgesEl: null, - /** - * The area for sharing bridge addresses. - * - * @type {Element?} - */ - _shareEl: null, - /** - * The two headings for the bridge settings. - * - * One heading is shown during a search, the other is shown otherwise. - * - * @type {?Element[]} - */ - _bridgesSettingsHeadings: null, - /** - * The two headings for the current bridges, at the start of the area. - * - * One heading is shown during a search, the other is shown otherwise. - * - * @type {Element?} - */ - _currentBridgesHeadings: null, - /** - * The area for showing no bridges. - * - * @type {Element?} - */ - _noBridgesEl: null, - /** - * The heading elements for changing bridges. - * - * One heading is shown during a search, the other is shown otherwise. - * - * @type {?Element[]} - */ - _changeHeadingEls: null, - /** - * The button for user to provide a bridge address or share code. - * - * @type {Element?} - */ - _userProvideButton: null, - /** - * A map from the bridge source to its corresponding label. - * - * @type {?Map<number, Element>} - */ - _sourceLabels: null, - - /** - * Initialize the bridge settings. - */ - init() { - gBridgesNotification.init(); - - this._bridgesSettingsHeadings = Array.from( - document.querySelectorAll(".tor-bridges-subcategory-heading") - ); - this._currentBridgesHeadings = Array.from( - document.querySelectorAll(".tor-bridges-current-heading") - ); - this._bridgesEl = document.getElementById("tor-bridges-current"); - this._noBridgesEl = document.getElementById("tor-bridges-none"); - this._groupEl = document.getElementById("torPreferences-bridges-group"); - - this._sourceLabels = new Map([ - [ - TorBridgeSource.BuiltIn, - document.getElementById("tor-bridges-built-in-label"), - ], - [ - TorBridgeSource.UserProvided, - document.getElementById("tor-bridges-user-label"), - ], - [ - TorBridgeSource.BridgeDB, - document.getElementById("tor-bridges-requested-label"), - ], - [TorBridgeSource.Lox, document.getElementById("tor-bridges-lox-label")], - ]); - this._shareEl = document.getElementById("tor-bridges-share"); - - this._toggleButton = document.getElementById("tor-bridges-enabled-toggle"); - // Initially disabled whilst TorSettings may not be initialized. - this._toggleButton.disabled = true; - - this._toggleButton.addEventListener("toggle", () => { - if (!this._haveBridges) { - return; - } - TorSettings.changeSettings({ - bridges: { enabled: this._toggleButton.pressed }, - }); - }); - - this._changeHeadingEls = Array.from( - document.querySelectorAll(".tor-bridges-change-heading") - ); - this._userProvideButton = document.getElementById( - "tor-bridges-open-user-provide-dialog-button" - ); - - document.l10n.setAttributes( - document.getElementById("tor-bridges-user-provide-description"), - // TODO: Set a different string if we have Lox enabled. - "tor-bridges-add-addresses-description" - ); - - // TODO: Change to GetLoxBridges if Lox enabled, and the account is set up. - const telegramUserName = "GetBridgesBot"; - const telegramInstruction = document.getElementById( - "tor-bridges-provider-instruction-telegram" - ); - telegramInstruction.querySelector("a").href = - `https://t.me/${telegramUserName}`; - document.l10n.setAttributes( - telegramInstruction, - "tor-bridges-provider-telegram-instruction", - { telegramUserName } - ); - - document - .getElementById("tor-bridges-open-built-in-dialog-button") - .addEventListener("click", () => { - this._openBuiltinDialog(); - }); - this._userProvideButton.addEventListener("click", () => { - this._openUserProvideDialog(this._haveBridges ? "replace" : "add"); - }); - document - .getElementById("tor-bridges-open-request-dialog-button") - .addEventListener("click", () => { - this._openRequestDialog(); - }); - - Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged); - - gBridgeGrid.init(); - gBuiltinBridgesArea.init(); - gLoxStatus.init(); - - this._initBridgesMenu(); - this._initShareArea(); - - // NOTE: Before initializedPromise completes, the current bridges sections - // should be hidden. - // And gBridgeGrid and gBuiltinBridgesArea are not active. - TorSettings.initializedPromise.then(() => { - this._updateEnabled(); - this._updateBridgeStrings(); - this._updateSource(); - }); - }, - - /** - * Un-initialize the bridge settings. - */ - uninit() { - gBridgeGrid.uninit(); - gBuiltinBridgesArea.uninit(); - gLoxStatus.uninit(); - - Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged); - }, - - observe(subject, topic) { - switch (topic) { - case TorSettingsTopics.SettingsChanged: { - const { changes } = subject.wrappedJSObject; - if (changes.includes("bridges.enabled")) { - this._updateEnabled(); - } - if (changes.includes("bridges.source")) { - this._updateSource(); - } - if (changes.includes("bridges.bridge_strings")) { - this._updateBridgeStrings(); - } - break; - } - } - }, - - /** - * Update whether the bridges should be shown as enabled. - */ - _updateEnabled() { - // Changing the pressed property on moz-toggle should not trigger its - // "toggle" event. - this._toggleButton.pressed = TorSettings.bridges.enabled; - }, - - /** - * The shown bridge source. - * - * Initially null to indicate that it is unset for the first call to - * _updateSource. - * - * @type {integer?} - */ - _bridgeSource: null, - /** - * Whether the user is encouraged to share their bridge addresses. - * - * @type {boolean} - */ - _canShare: false, - - /** - * Update _bridgeSource. - */ - _updateSource() { - // NOTE: This should only ever be called after TorSettings is already - // initialized. - const bridgeSource = TorSettings.bridges.source; - if (bridgeSource === this._bridgeSource) { - // Avoid re-activating an area if the source has not changed. - return; - } - - this._bridgeSource = bridgeSource; - - // Before hiding elements, we determine whether our region contained the - // user focus. - const hadFocus = - this._bridgesEl.contains(document.activeElement) || - this._noBridgesEl.contains(document.activeElement); - - for (const [source, labelEl] of this._sourceLabels.entries()) { - labelEl.hidden = source !== bridgeSource; - } - - this._canShare = - bridgeSource === TorBridgeSource.UserProvided || - bridgeSource === TorBridgeSource.BridgeDB; - - this._shareEl.hidden = !this._canShare; - - // Force the menu to close whenever the source changes. - // NOTE: If the menu had focus then hadFocus will be true, and focus will be - // re-assigned. - this._forceCloseBridgesMenu(); - - // Update whether we have bridges. - this._updateHaveBridges(); - - if (hadFocus) { - // Always reset the focus to the start of the area whenever the source - // changes. - // NOTE: gBuiltinBridges._updateBridgeType and gBridgeGrid._updateRows - // may have already called takeFocus in response to them being - // de-activated. The re-call should be safe. - this.takeFocus(); - } - }, - - /** - * Whether we have bridges or not, or null if it is unknown. - * - * @type {boolean?} - */ - _haveBridges: null, - - /** - * Update the _haveBridges value. - */ - _updateHaveBridges() { - // NOTE: We use the TorSettings.bridges.source value, rather than - // this._bridgeSource because _updateHaveBridges can be called just before - // _updateSource (via takeFocus). - const haveBridges = TorSettings.bridges.source !== TorBridgeSource.Invalid; - - if (haveBridges === this._haveBridges) { - return; - } - - this._haveBridges = haveBridges; - - this._toggleButton.disabled = !haveBridges; - // Add classes to show or hide the "no bridges" and "Your bridges" sections. - // NOTE: Before haveBridges is set, neither class is added, so both sections - // and hidden. - this._groupEl.classList.add("bridges-initialized"); - this._bridgesEl.hidden = !haveBridges; - this._noBridgesEl.hidden = haveBridges; - - for (const headingEl of this._changeHeadingEls) { - document.l10n.setAttributes( - headingEl, - haveBridges - ? "tor-bridges-replace-bridges-heading" - : "tor-bridges-add-bridges-heading" - ); - } - document.l10n.setAttributes( - this._userProvideButton, - haveBridges ? "tor-bridges-replace-button" : "tor-bridges-add-new-button" - ); - }, - - /** - * Force the focus to move to the bridge area. - */ - takeFocus() { - if (this._haveBridges === null) { - // The bridges area has not been initialized yet, which means that - // TorSettings may not be initialized. - // Unexpected to receive a call before then, so just return early. - return; - } - - // Make sure we have the latest value for _haveBridges. - // We also ensure that the _currentBridgesHeadings element is visible before - // we focus it. - this._updateHaveBridges(); - - // 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. - // NOTE: We have two headings: one shown during a search and one shown - // otherwise. We focus the heading that is currently visible. - // See tor-browser#43320. - // 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. - const focusHeadings = this._haveBridges - ? this._currentBridgesHeadings // The heading above the new bridges. - : this._bridgesSettingsHeadings; // The top of the bridge settings. - if (focusHeadings[0].checkVisibility({ visibilityProperty: true })) { - focusHeadings[0].focus(); - } else { - focusHeadings[1].focus(); - } - }, - - /** - * The bridge strings in a copy-able form. - * - * @type {string} - */ - _bridgeStrings: "", - /** - * Whether the bridge strings should be shown as a QR code. - * - * @type {boolean} - */ - _canQRBridges: false, - - /** - * Update the stored bridge strings. - */ - _updateBridgeStrings() { - const bridges = TorSettings.bridges.bridge_strings; - - this._bridgeStrings = bridges.join("\n"); - // TODO: Determine what logic we want. - this._canQRBridges = bridges.length <= 3; - - this._qrButton.disabled = !this._canQRBridges; - }, - - /** - * Copy all the bridge addresses to the clipboard. - */ - _copyBridges() { - const clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( - Ci.nsIClipboardHelper - ); - clipboard.copyString(this._bridgeStrings); - }, - - /** - * Open the QR code dialog encoding all the bridge addresses. - */ - _openQR() { - if (!this._canQRBridges) { - return; - } - showBridgeQr(this._bridgeStrings); - }, - - /** - * The QR button for copying all QR codes. - * - * @type {Element?} - */ - _qrButton: null, - - _initShareArea() { - document - .getElementById("tor-bridges-copy-addresses-button") - .addEventListener("click", () => { - this._copyBridges(); - }); - - this._qrButton = document.getElementById("tor-bridges-qr-addresses-button"); - this._qrButton.addEventListener("click", () => { - this._openQR(); - }); - }, - - /** - * The menu for all bridges. - * - * @type {Element?} - */ - _bridgesMenu: null, - - /** - * Initialize the menu for all bridges. - */ - _initBridgesMenu() { - this._bridgesMenu = document.getElementById("tor-bridges-all-options-menu"); - - // NOTE: We generally assume that once the bridge menu is opened the - // this._bridgeStrings value will not change. - const qrItem = document.getElementById( - "tor-bridges-options-qr-all-menu-item" - ); - qrItem.addEventListener("click", () => { - this._openQR(); - }); - - const copyItem = document.getElementById( - "tor-bridges-options-copy-all-menu-item" - ); - copyItem.addEventListener("click", () => { - this._copyBridges(); - }); - - const editItem = document.getElementById( - "tor-bridges-options-edit-all-menu-item" - ); - editItem.addEventListener("click", () => { - this._openUserProvideDialog("edit"); - }); - - // TODO: Do we want a different item for built-in bridges, rather than - // "Remove all bridges"? - document - .getElementById("tor-bridges-options-remove-all-menu-item") - .addEventListener("click", async () => { - // TODO: Should we only have a warning when not built-in? - const parentWindow = - Services.wm.getMostRecentWindow("navigator:browser"); - const flags = - Services.prompt.BUTTON_POS_0 * - Services.prompt.BUTTON_TITLE_IS_STRING + - Services.prompt.BUTTON_POS_0_DEFAULT + - Services.prompt.BUTTON_DEFAULT_IS_DESTRUCTIVE + - Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL; - - const [titleString, bodyString, removeString] = - await document.l10n.formatValues([ - { id: "remove-all-bridges-warning-title" }, - { id: "remove-all-bridges-warning-description" }, - { id: "remove-all-bridges-warning-remove-button" }, - ]); - - // TODO: Update the text, and remove old strings. - const buttonIndex = Services.prompt.confirmEx( - parentWindow, - titleString, - bodyString, - flags, - removeString, - null, - null, - null, - {} - ); - - if (buttonIndex !== 0) { - return; - } - - TorSettings.changeSettings({ - // This should always have the side effect of disabling bridges as - // well. - bridges: { source: TorBridgeSource.Invalid }, - }); - }); - - this._bridgesMenu.addEventListener("showing", () => { - qrItem.hidden = !this._canShare || !this._canQRBridges; - editItem.hidden = this._bridgeSource !== TorBridgeSource.UserProvided; - }); - - const bridgesMenuButton = document.getElementById( - "tor-bridges-all-options-button" - ); - bridgesMenuButton.addEventListener("click", event => { - this._bridgesMenu.toggle(event, bridgesMenuButton); - }); - - this._bridgesMenu.addEventListener("hidden", () => { - // Make sure the button receives focus again when the menu is hidden. - // Currently, panel-list.js only does this when the menu is opened with a - // keyboard, but this causes focus to be lost from the page if the user - // uses a mixture of keyboard and mouse. - bridgesMenuButton.focus(); - }); - }, - - /** - * Force the bridges menu to close. - */ - _forceCloseBridgesMenu() { - this._bridgesMenu.hide(null, { force: true }); - }, - - /** - * Open a bridge dialog that will change the users bridges. - * - * @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. - */ - _openDialog(url, inputData, onAccept) { - const result = { accepted: false, connect: false }; - let savedSettings = null; - 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. - 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(() => gBridgeSettings.takeFocus()); - }, - }, - result, - inputData - ); - }, - - /** - * Open the built-in bridge dialog. - */ - _openBuiltinDialog() { - this._openDialog( - "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml", - null, - result => { - if (!result.type) { - return null; - } - return TorSettings.changeSettings({ - bridges: { - enabled: true, - source: TorBridgeSource.BuiltIn, - builtin_type: result.type, - }, - }); - } - ); - }, - - /* - * Open the request bridge dialog. - */ - _openRequestDialog() { - this._openDialog( - "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml", - null, - result => { - if (!result.bridges?.length) { - return null; - } - return TorSettings.changeSettings({ - bridges: { - enabled: true, - source: TorBridgeSource.BridgeDB, - bridge_strings: result.bridges, - }, - }); - } - ); - }, - - /** - * Open the user provide dialog. - * - * @param {string} mode - The mode to open the dialog in: "add", "replace" or - * "edit". - */ - _openUserProvideDialog(mode) { - this._openDialog( - "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 = TorBridgeSource.Lox; - bridges.lox_id = loxId; - } else { - bridges.source = TorBridgeSource.UserProvided; - bridges.bridge_strings = result.addresses; - } - return TorSettings.changeSettings({ bridges }); - } - ); - }, -}; - -/** - * Area to show the internet and tor network connection status. - */ -const gNetworkStatus = { - /** - * Initialize the area. - */ - init() { - this._internetAreaEl = document.getElementById( - "network-status-internet-area" - ); - this._internetResultEl = this._internetAreaEl.querySelector( - ".network-status-result" - ); - - this._torAreaEl = document.getElementById("network-status-tor-area"); - this._torResultEl = this._torAreaEl.querySelector(".network-status-result"); - this._torConnectButton = document.getElementById( - "network-status-tor-connect-button" - ); - this._torConnectButton.addEventListener("click", () => { - TorConnectParent.open({ beginBootstrapping: "soft" }); - }); - - this._updateInternetStatus(); - this._updateTorConnectionStatus(); - - Services.obs.addObserver(this, TorConnectTopics.StageChange); - Services.obs.addObserver(this, TorConnectTopics.InternetStatusChange); - }, - - /** - * Un-initialize the area. - */ - uninit() { - Services.obs.removeObserver(this, TorConnectTopics.StageChange); - Services.obs.removeObserver(this, TorConnectTopics.InternetStatusChange); - }, - - observe(subject, topic) { - switch (topic) { - // triggered when tor connect state changes and we may - // need to update the messagebox - case TorConnectTopics.StageChange: - this._updateTorConnectionStatus(); - break; - case TorConnectTopics.InternetStatusChange: - this._updateInternetStatus(); - break; - } - }, - - /** - * Update the shown internet status. - */ - _updateInternetStatus() { - let l10nId; - let isOffline = false; - switch (TorConnect.internetStatus) { - case InternetStatus.Offline: - l10nId = "tor-connection-internet-status-offline"; - isOffline = true; - break; - case InternetStatus.Online: - l10nId = "tor-connection-internet-status-online"; - break; - default: - l10nId = "tor-connection-internet-status-unknown"; - break; - } - this._internetResultEl.setAttribute("data-l10n-id", l10nId); - this._internetAreaEl.classList.toggle("status-offline", isOffline); - }, - - /** - * Update the shown Tor connection status. - */ - _updateTorConnectionStatus() { - const buttonHadFocus = this._torConnectButton.contains( - document.activeElement - ); - const isBootstrapped = - TorConnect.stageName === TorConnectStage.Bootstrapped; - const isBlocked = !isBootstrapped && TorConnect.potentiallyBlocked; - let l10nId; - if (isBootstrapped) { - l10nId = "tor-connection-network-status-connected"; - } else if (isBlocked) { - l10nId = "tor-connection-network-status-blocked"; - } else { - l10nId = "tor-connection-network-status-not-connected"; - } - - document.l10n.setAttributes(this._torResultEl, l10nId); - this._torAreaEl.classList.toggle("status-connected", isBootstrapped); - this._torAreaEl.classList.toggle("status-blocked", isBlocked); - if (isBootstrapped && buttonHadFocus) { - // Button has become hidden and will loose focus. Most likely this has - // happened because the user clicked the button to open about:torconnect. - // Since this is near the top of the page, we move focus to the search - // input (for when the user returns). - gSearchResultsPane.searchInput.focus(); - } - }, -}; - -/* - Connection Pane - - Code for populating the XUL in about:preferences#connection, handling input events, interfacing with tor-launcher -*/ -const gConnectionPane = (function () { - /* CSS selectors for all of the Tor Network DOM elements we need to access */ - const selectors = { - bridges: { - locationGroup: "#torPreferences-bridges-locationGroup", - locationLabel: "#torPreferences-bridges-locationLabel", - location: "#torPreferences-bridges-location", - locationEntries: "#torPreferences-bridges-locationEntries", - chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe", - }, - }; /* selectors */ - - const retval = { - // cached frequently accessed DOM elements - _enableQuickstartToggle: null, - - // populate xul with strings and cache the relevant elements - _populateXUL() { - // Quickstart - this._enableQuickstartToggle = document.getElementById( - "tor-connection-quickstart-toggle" - ); - this._enableQuickstartToggle.addEventListener("toggle", () => { - TorConnect.quickstart = this._enableQuickstartToggle.pressed; - }); - this._enableQuickstartToggle.pressed = TorConnect.quickstart; - Services.obs.addObserver(this, TorConnectTopics.QuickstartChange); - - // Location - { - const prefpane = document.getElementById("mainPrefPane"); - - const locationGroup = prefpane.querySelector( - selectors.bridges.locationGroup - ); - prefpane.querySelector(selectors.bridges.locationLabel).textContent = - TorStrings.settings.bridgeLocation; - const location = prefpane.querySelector(selectors.bridges.location); - const locationEntries = prefpane.querySelector( - selectors.bridges.locationEntries - ); - const chooseForMe = prefpane.querySelector( - selectors.bridges.chooseForMe - ); - chooseForMe.setAttribute( - "label", - TorStrings.settings.bridgeChooseForMe - ); - chooseForMe.addEventListener("command", () => { - if (!location.value) { - return; - } - TorConnectParent.open({ - beginBootstrapping: "hard", - regionCode: location.value, - }); - }); - const createItem = (value, label, disabled) => { - const item = document.createXULElement("menuitem"); - item.setAttribute("value", value); - item.setAttribute("label", label); - if (disabled) { - item.setAttribute("disabled", "true"); - } - return item; - }; - - // TODO: Re-fetch when intl:app-locales-changed is fired, if we keep - // this after tor-browser#42477. - const regionNames = TorConnect.getRegionNames(); - const addLocations = codes => { - const items = []; - for (const code of codes) { - items.push(createItem(code, regionNames[code] || code)); - } - items.sort((left, right) => left.label.localeCompare(right.label)); - locationEntries.append(...items); - }; - // Add automatic before waiting for getFrequentRegions. - locationEntries.append( - createItem("automatic", TorStrings.settings.bridgeLocationAutomatic) - ); - location.value = "automatic"; - TorConnect.getFrequentRegions().then(frequentCodes => { - locationEntries.append( - createItem("", TorStrings.settings.bridgeLocationFrequent, true) - ); - addLocations(frequentCodes); - locationEntries.append( - createItem("", TorStrings.settings.bridgeLocationOther, true) - ); - addLocations(Object.keys(regionNames)); - }); - this._showAutoconfiguration = () => { - locationGroup.hidden = - !TorConnect.canBeginAutoBootstrap || !TorConnect.potentiallyBlocked; - }; - this._showAutoconfiguration(); - } - - // Advanced setup - document - .getElementById("torPreferences-advanced-button") - .addEventListener("click", () => { - this.onAdvancedSettings(); - }); - - // Tor logs - document - .getElementById("torPreferences-buttonTorLogs") - .addEventListener("click", () => { - this.onViewTorLogs(); - }); - - Services.obs.addObserver(this, TorConnectTopics.StageChange); - }, - - init() { - gBridgeSettings.init(); - gNetworkStatus.init(); - - this._populateXUL(); - - const onUnload = () => { - window.removeEventListener("unload", onUnload); - gConnectionPane.uninit(); - }; - window.addEventListener("unload", onUnload); - }, - - uninit() { - gBridgeSettings.uninit(); - gNetworkStatus.uninit(); - - // unregister our observer topics - Services.obs.removeObserver(this, TorConnectTopics.QuickstartChange); - Services.obs.removeObserver(this, TorConnectTopics.StageChange); - }, - - // whether the page should be present in about:preferences - get enabled() { - return TorConnect.enabled; - }, - - // - // Callbacks - // - - observe(subject, topic) { - switch (topic) { - case TorConnectTopics.QuickstartChange: { - this._enableQuickstartToggle.pressed = TorConnect.quickstart; - break; - } - // triggered when tor connect state changes and we may - // need to update the messagebox - case TorConnectTopics.StageChange: { - this._showAutoconfiguration(); - break; - } - } - }, - - async onAdvancedSettings() { - // Ensure TorSettings is complete before loading the dialog, which reads - // from TorSettings. - await TorSettings.initializedPromise; - gSubDialog.open( - "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml", - { features: "resizable=yes" } - ); - }, - - onViewTorLogs() { - gSubDialog.open( - "chrome://browser/content/torpreferences/torLogDialog.xhtml", - { features: "resizable=yes" } - ); - }, - }; - return retval; -})(); /* gConnectionPane */ ===================================== browser/components/torpreferences/content/torPreferences.css ===================================== @@ -28,108 +28,14 @@ button.spoof-button-disabled { } } -.tor-toggle { - margin-block: var(--space-large); - width: max-content; -} - -/* Status */ - -#network-status-internet-area { - margin-block: var(--space-large); -} - -#network-status-tor-area { - margin-block: 0 var(--space-xxlarge); -} - -.network-status-area { - display: flex; - align-items: center; - white-space: nowrap; -} - -.network-status-area > * { - flex: 0 0 auto; -} - -.network-status-icon { - width: var(--icon-size); - height: var(--icon-size); - margin-inline-end: var(--space-small); - -moz-context-properties: fill, stroke; - fill: var(--icon-color); - stroke: var(--icon-color); -} - -#network-status-internet-area .network-status-icon { - content: url("chrome://browser/content/torconnect/network.svg"); -} - -#network-status-internet-area.status-offline .network-status-icon { - content: url("chrome://browser/content/torconnect/network-broken.svg"); -} - -#network-status-tor-area .network-status-icon { - content: url("chrome://browser/content/torconnect/tor-connect.svg"); -} - -#network-status-tor-area:not(.status-connected) .network-status-icon { - content: url("chrome://browser/content/torconnect/tor-connect-broken.svg"); -} - -#network-status-tor-area.status-blocked .network-status-icon { - /* Same as .tor-connect-status-potentially-blocked. */ - stroke: var(--icon-color-critical); -} - -.network-status-label { - font-weight: var(--font-weight-bold); - margin-inline-end: var(--space-medium); -} - -.network-status-result { - margin-inline-end: var(--space-medium); -} - -#network-status-tor-area.status-connected #network-status-tor-connect-button { - /* Hide button when already connected. */ - display: none; -} - /* Bridge settings */ -.tor-medium-heading { - /* Same font size as mozilla preferences h2. */ - font-size: var(--font-size-large); - font-weight: var(--font-weight-bold); - margin: 0; -} - .tor-small-heading { font-size: inherit; font-weight: var(--font-weight-bold); margin: 0; } -/* Hide the tor-search-heading elements when the group's search header is - * hidden. These only appear in search results. - * See tor-browser#43320. - * NOTE: `.search-header[hidden] ~ :is(* .tor-search-heading)` will not match - * (possibly because the `~` selector is unsure how to integrate with the - * non-compound `* .tor-search-heading` selector). So we need to duplicate the - * `.search-header[hidden]` rule. */ -#torPreferences-bridges-group :is(.search-header[hidden] ~ * .tor-search-heading, .search-header[hidden] ~ .tor-search-heading) { - display: none; -} - -/* Hide the tor-non-search-heading elements when the group's search header is - * not hidden. These only appear outside of search results. - * See tor-browser#43320. */ -#torPreferences-bridges-group :is(.search-header:not([hidden]) ~ * .tor-non-search-heading, .search-header:not([hidden]) ~ .tor-non-search-heading) { - display: none; -} - .tor-focusable-heading { /* Do not occupy more horizontal space than necessary. */ width: fit-content; @@ -188,25 +94,6 @@ button.spoof-button-disabled { display: none; } -#tor-bridges-update-area { - /* Still accessible to screen reader, but not visual. */ - position: absolute; - clip-path: inset(50%); -} - -#torPreferences-bridges-group:not(.bridges-initialized) { - /* Hide bridge settings whilst not initialized. */ - display: none; -} - -#tor-bridges-none, -#tor-bridges-current { - @media not -moz-pref("browser.settings-redesign.enabled") { - margin-inline: 0; - margin-block: var(--space-xxlarge); - } -} - #tor-bridges-none:not([hidden]) { display: grid; justify-items: center; @@ -217,11 +104,6 @@ button.spoof-button-disabled { border-radius: var(--border-radius-small); color: var(--text-color-deemphasized); border: 2px dashed var(--border-color-deemphasized); - - @media not -moz-pref("browser.settings-redesign.enabled") { - padding-block: 64px; - gap: var(--space-large); - } } #tor-bridges-none-icon { @@ -232,33 +114,10 @@ button.spoof-button-disabled { fill: currentColor; } -.tor-bridges-box, .tor-bridges-details-box { padding: var(--space-large); border-radius: var(--border-radius-medium); border: var(--border-width) solid var(--border-color); - - @media not -moz-pref("browser.settings-redesign.enabled") { - border-radius: var(--border-radius-small); - background: var(--background-color-box-info); - } -} - -@media not forced-colors { - .tor-bridges-box { - border-color: transparent; - } -} - -#tor-bridges-current-header-bar { - display: grid; - min-width: max-content; - grid-template: "heading source button" min-content / max-content 1fr max-content; - align-items: center; - border-block-end: var(--border-width) solid var(--border-color); - padding-block-end: var(--space-large); - margin-block-end: var(--space-large); - white-space: nowrap; } tor-bridges-display { @@ -685,63 +544,6 @@ tor-bridges-display { align-self: center; } -.tor-bridges-provider-heading { - margin-block: var(--space-xxlarge) var(--space-small); -} - -#tor-bridges-provider-area { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--space-large); - align-items: start; - margin-block-start: var(--space-xlarge); -} - -#tor-bridges-provider-list { - display: grid; - grid-template-columns: max-content max-content; - gap: var(--space-xlarge) var(--space-medium); - margin-block: var(--space-large); - padding: 0; -} - -.tor-bridges-provider-item { - grid-column: 1 / -1; - display: grid; - grid-template-columns: subgrid; - align-items: center; - justify-items: start; - gap: var(--space-small) var(--space-medium); -} - -.tor-bridges-provider-icon { - width: var(--icon-size); - height: var(--icon-size); - -moz-context-properties: fill; - fill: var(--icon-color); -} - -#tor-bridges-provider-icon-telegram { - content: url("chrome://browser/content/torpreferences/telegram-logo.svg"); -} - -#tor-bridges-provider-icon-web { - content: url("chrome://browser/content/torconnect/network.svg"); -} - -#tor-bridges-provider-icon-email { - content: url("chrome://browser/content/torpreferences/mail.svg"); -} - -.tor-bridges-provider-name { - font-weight: var(--font-weight-bold); - font-size: var(--font-size-small); -} - -.tor-bridges-provider-instruction { - grid-column: 2 / 3; -} - #torBridgesRequestBanner { display: flex; flex-direction: column; @@ -760,37 +562,6 @@ tor-bridges-display { } } -#tor-bridges-request-box { - /* Take up the full height in the container. */ - align-self: stretch; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - justify-content: center; -} - -#tor-bridges-request-box > * { - flex: 0 0 auto; -} - -#tor-bridges-request-description { - margin-block: var(--space-medium) var(--space-large); -} - -#tor-bridges-open-request-dialog-button { - margin: 0; -} - -#torPreferences-bridges-location { - width: 280px; -} - -#torPreferences-bridges-location menuitem[disabled="true"] { - color: var(--button-text-color, inherit); - font-weight: var(--font-weight-bold); -} - /* Request bridges */ image#torPreferences-requestBridge-captchaImage { @@ -973,11 +744,6 @@ image#torPreferences-requestBridge-errorIcon { fill: var(--icon-color-warning); } -groupbox#torPreferences-bridges-group textarea { - white-space: pre; - overflow: auto; -} - /* Provide bridge dialog */ #user-provide-bridge-dialog:not(.show-entry-page) #user-provide-bridge-entry-page { ===================================== browser/components/torpreferences/jar.mn ===================================== @@ -30,7 +30,6 @@ browser.jar: content/browser/torpreferences/widgets/tor-connection-assist-banner.mjs (widgets/tor-connection-assist-banner.mjs) content/browser/torpreferences/widgets/tor-connection-status.mjs (widgets/tor-connection-status.mjs) content/browser/torpreferences/widgets/tor-connection-status.css (widgets/tor-connection-status.css) - content/browser/torpreferences/connectionPane.js (content/connectionPane.js) content/browser/torpreferences/torPreferences.css (content/torPreferences.css) content/browser/torpreferences/bridgemoji/BridgeEmoji.js (content/bridgemoji/BridgeEmoji.js) content/browser/torpreferences/bridgemoji/bridge-emojis.json (content/bridgemoji/bridge-emojis.json) ===================================== toolkit/locales/en-US/toolkit/global/tor-browser.ftl ===================================== @@ -101,11 +101,6 @@ tor-connection-settings-pane = # "Connection" refers to the Tor Browser's connection to the Tor network. tor-connection-settings-nav-button = Connection .title = Connection -# -brand-short-name refers to 'Tor Browser', localized. -tor-connection-overview = { -brand-short-name } routes your traffic over the Tor network, run by thousands of volunteers around the world. -tor-connection-browser-learn-more-link = Learn more -tor-connection-automatic-heading = Connect automatically -tor-connection-automatic-description = Automatically connect to the Tor network at launch using your current connection settings. tor-connection-quickstart-checkbox = .label = Always connect automatically @@ -138,10 +133,6 @@ tor-connection-network-status-not-connected = Not connected # Shown when the user's Tor connection may be blocked. # Uses sentence case in English (US). tor-connection-network-status-blocked = Potentially blocked -# Button shown when we are not yet connected to the Tor network. -# It will open a page to start connecting to the Tor network. -# Uses sentence case in English (US). -tor-connection-network-status-connect-button = Connect # Here "Connect" refers to connecting to the Tor network. # Uses sentence case in English (US). tor-connection-status-connect-button = @@ -149,9 +140,6 @@ tor-connection-status-connect-button = ## Tor Bridges Settings. -tor-bridges-heading = Bridges -tor-bridges-overview = Bridges help you securely access the Tor network in places where Tor is blocked. Depending on where you are, one bridge may work better than another. -tor-bridges-learn-more-link = Learn more tor-bridges-group = .label = Bridges .description = Bridges help you securely access the Tor network in places where Tor is blocked. Depending on where you are, one bridge may work better than another. @@ -232,10 +220,6 @@ tor-bridges-menu-item-copy-address = Copy bridge address tor-bridges-menu-item-remove-bridge = Remove bridge .accesskey = R -# Text shown just before a description of the most recent change to the list of user's bridges. Some white space will separate this text from the change description. -# This text is not visible, but is instead used for screen reader users. -# E.g. in English this could be "Recent update: One of your Tor bridges has been removed." -tor-bridges-update-area-intro = Recent update: # Update text for screen reader users when only one of their bridges has been removed. tor-bridges-update-removed-one-bridge = One of your Tor bridges has been removed. # Update text for screen reader users when all of their bridges have been removed. @@ -313,74 +297,33 @@ tor-bridges-lox-new-invites = # Button for the user to acknowledge a change in their "bridge pass". tor-bridges-lox-got-it-button = Got it - -# Shown as a heading when the user has no current bridges. -tor-bridges-add-bridges-heading = Add bridges -# Shown as a heading when the user has existing bridges that can be replaced. -tor-bridges-replace-bridges-heading = Replace your bridges # Shown as a heading when the user has no current bridges. tor-bridges-add-bridges-group = .label = Add bridges # Shown as a heading when the user has existing bridges that can be replaced. tor-bridges-replace-bridges-group = .label = Replace your bridges - -# -brand-short-name refers to 'Tor Browser', localized. -tor-bridges-select-built-in-description = Choose from one of { -brand-short-name }’s built-in bridges # -brand-short-name refers to 'Tor Browser', localized. tor-bridges-choose-built-in-button = .label = Choose from one of { -brand-short-name }’s built-in bridges -tor-bridges-select-built-in-button = Select a built-in bridge… - -tor-bridges-add-addresses-description = Enter bridge addresses you already know tor-bridges-enter-bridges-button = .label = Enter bridge addresses you already know -# Shown when the user has no current bridges. -# Opens a dialog where the user can provide a new bridge address or share code. -tor-bridges-add-new-button = Add new bridges… -# Shown when the user has existing bridges. -# Opens a dialog where the user can provide a new bridge address or share code to replace their current bridges. -tor-bridges-replace-button = Replace bridges… - -tor-bridges-find-more-heading = Find more bridges -# "Tor Project" is the organisation name. -tor-bridges-find-more-description = Since many bridge addresses aren’t public, you may need to request some from the Tor Project. + # "Tor Project" is the organisation name. tor-bridges-find-more-group = .label = Find more bridges .description = Since many bridge addresses aren’t public, you may need to request some from the Tor Project. - -# "Telegram" is the common brand name of the Telegram Messenger application -tor-bridges-provider-telegram-name = Telegram -# Here "Message" is a verb, short for "Send a message to". This is an instruction to send a message to the given Telegram Messenger user to receive a new bridge. -# $telegramUserName (String) - The Telegram Messenger user name that should receive messages. Should be wrapped in '<a data-l10n-name="user">' and '</a>'. -# E.g. in English, "Message GetBridgesBot". -tor-bridges-provider-telegram-instruction = Message <a data-l10n-name="user">{ $telegramUserName }</a> # "Telegram" is the common brand name of the Telegram Messenger application. # Here "Message" is a verb, short for "Send a message to". This is an instruction to send a message to the given Telegram Messenger user to receive a new bridge. # $telegramUserName (String) - The Telegram Messenger user name that should receive messages. tor-bridges-source-telegram-link = .label = Telegram .description = Message { $telegramUserName } - -# "Web" is the proper noun for the "World Wide Web". -tor-bridges-provider-web-name = Web -# Instructions to visit the given website. -# $url (String) - The URL for Tor Project bridges. Should be wrapped in '<a data-l10n-name"url">' and '</a>'. -tor-bridges-provider-web-instruction = Visit <a data-l10n-name="url">{ $url }</a> - # "Web" is the proper noun for the "World Wide Web". # $url (String) - The URL for Tor Project bridges. tor-bridges-source-web-link = .label = Web .description = Visit { $url } - -# "Gmail" is the Google brand name. "Riseup" refers to the Riseup organisation at riseup.net. -tor-bridges-provider-email-name = Gmail or Riseup -# Here "Email" is a verb, short for "Send an email to". This is an instruction to send an email to the given address to receive a new bridge. -# $address (String) - The email address that should receive the email. -# E.g. in English, "Email bridges@torproject.org". -tor-bridges-provider-email-instruction = Email { $address } # "Gmail" is the Google brand name. "Riseup" refers to the Riseup organisation at riseup.net. # Here "Email" is a verb, short for "Send an email to". This is an instruction to send an email to the given address to receive a new bridge. # $address (String) - The email address that should receive the email. @@ -514,16 +457,6 @@ tor-advanced-configure-button = .label = Configure how { -brand-short-name } connects to the internet tor-view-log-button2 = .label = View the Tor log -tor-advanced-settings-heading = Advanced -tor-advanced-settings-description = Configure how { -brand-short-name } connects to the internet. -# Button that opens the advanced connection settings dialog. -# Uses sentence case in English (US). -tor-advanced-settings-button = Settings… -# "log" is a noun, referring to the recorded text output of the Tor process. -tor-view-log-description = View the Tor log. -# "log" is a noun, referring to the recorded text output of the Tor process. -# Uses sentence case in English (US). -tor-view-log-button = View log… ## Tor log dialog. ===================================== toolkit/modules/TorStrings.sys.mjs ===================================== @@ -70,13 +70,7 @@ const Loader = { // Message box torPreferencesDescription: "Tor Browser routes your traffic over the Tor network, run by thousands of volunteers around the world.", - // Quickstart quickstartCheckbox: "Always connect automatically", - bridgeLocation: "Your location", - bridgeLocationAutomatic: "Automatic", - bridgeLocationFrequent: "Frequently selected locations", - bridgeLocationOther: "Other locations", - bridgeChooseForMe: "Choose a Bridge For Me…", }; const tsb = new TorPropertyStringBundle( ===================================== toolkit/torbutton/chrome/locale/en-US/settings.properties ===================================== @@ -9,11 +9,3 @@ settings.torPreferencesDescription=Tor Browser routes your traffic over the Tor network, run by thousands of volunteers around the world. settings.quickstartCheckbox=Always connect automatically - -# Might be removed in tor-browser#42477 - -settings.bridgeLocation=Your location -settings.bridgeLocationAutomatic=Automatic -settings.bridgeLocationFrequent=Frequently selected locations -settings.bridgeLocationOther=Other locations -settings.bridgeChooseForMe=Choose a Bridge For Me… View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8f8c349... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/8f8c349... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help