Loading browser/components/preferences/preferences.js +1 −1 Original line number Diff line number Diff line Loading @@ -271,7 +271,7 @@ const CONFIG_PANES = Object.freeze({ connection: { l10nId: "tor-connection-settings-pane", iconSrc: "chrome://browser/content/torconnect/tor-connect.svg", groupIds: ["connectionStatus"], groupIds: ["connectionStatus", "torBridges"], module: "chrome://browser/content/torpreferences/config/connection.mjs", visible: () => { return TorConnect.enabled; Loading browser/components/preferences/preferences.xhtml +1 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,7 @@ <script type="module" src="chrome://browser/content/preferences/widgets/update-information.mjs"></script> <script type="module" src="chrome://browser/content/preferences/widgets/update-state.mjs"></script> <script type="module" src="chrome://browser/content/ipprotection/bandwidth-usage.mjs"></script> <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-bridges-display.mjs"></script> <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-connection-status.mjs"></script> <script src="chrome://browser/content/torpreferences/bridgemoji/BridgeEmoji.js"/> </head> Loading browser/components/torpreferences/config/connection.mjs +413 −0 Original line number Diff line number Diff line Loading @@ -4,11 +4,33 @@ import { Preferences } from "chrome://global/content/preferences/Preferences.mjs const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { InternetStatus: "moz-src:///toolkit/modules/TorConnect.sys.mjs", openBridgeDialog: "chrome://browser/content/torpreferences/config/helpers.mjs", openUserProvideBridgeDialog: "chrome://browser/content/torpreferences/config/helpers.mjs", TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs", TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TorConnectStage: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TorConnectTopics: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TorProviderBuilder: "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs", TorProviderState: "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs", TorProviderTopics: "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs", TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs", TorSettingsTopics: "moz-src:///toolkit/modules/TorSettings.sys.mjs", }); // TODO: Change to GetLoxBridges if Lox enabled, and the account is set up. const TELEGRAM_USER_NAME = "GetBridgesBot"; const TELEGRAM_HREF = `https://t.me/${TELEGRAM_USER_NAME}`; const TOR_BRIDGES_URL_NAME = "bridges.torproject.org"; const TOR_BRIDGES_HREF = "https://bridges.torproject.org"; const TOR_BRIDGES_EMAIL = "bridges@torproject.org"; SettingGroupManager.registerGroups({ connectionStatus: { inProgress: true, Loading Loading @@ -39,6 +61,123 @@ SettingGroupManager.registerGroups({ }, ], }, torBridges: { inProgress: true, l10nId: "tor-bridges-group", supportPage: "tor-manual:bridges", headingLevel: 2, controlAttrs: { "focusable-heading": true }, items: [ { id: "bridgesEnabled", l10nId: "tor-bridges-use-bridges", control: "moz-toggle", }, { id: "torBridgesDisplay", control: "tor-bridges-display", }, { id: "newBridgesGroup", control: "moz-fieldset", controlAttrs: { headinglevel: 3, }, options: [ { control: "moz-box-group", items: [ { id: "builtinBridges", l10nId: "tor-bridges-choose-built-in-button", control: "moz-box-button", }, { id: "userProvidedBridges", l10nId: "tor-bridges-enter-bridges-button", control: "moz-box-button", }, ], }, ], }, { id: "findMoreBridgesGroup", l10nId: "tor-bridges-find-more-group", control: "moz-fieldset", controlAttrs: { headinglevel: 3, }, options: [ { control: "moz-box-item", options: [ { id: "torBridgesRequestBanner", control: "article", options: [ { control: "img", controlAttrs: { alt: "", src: "chrome://browser/content/torpreferences/bridge-bot.svg", }, }, { control: "p", l10nId: "tor-bridges-request-from-browser2", }, { // NOTE: We use the wrapping `div` to simply switch from the // `options` context to the `items` context, with the latter // wrapping the elements in a setting-control. control: "div", items: [ { id: "requestBridges", l10nId: "tor-bridges-request-button2", control: "moz-button", }, ], }, ], }, ], }, { control: "moz-box-group", options: [ { l10nId: "tor-bridges-source-telegram-link", l10nArgs: { telegramUserName: TELEGRAM_USER_NAME }, control: "moz-box-link", iconSrc: "chrome://browser/content/torpreferences/telegram-logo.svg", controlAttrs: { href: TELEGRAM_HREF, }, }, { l10nId: "tor-bridges-source-web-link", l10nArgs: { url: TOR_BRIDGES_URL_NAME }, control: "moz-box-link", iconSrc: "chrome://browser/content/torconnect/network.svg", controlAttrs: { href: TOR_BRIDGES_HREF, }, }, { l10nId: "tor-bridges-source-email-link", l10nArgs: { address: TOR_BRIDGES_EMAIL }, control: "moz-box-item", iconSrc: "chrome://browser/content/torpreferences/mail.svg", }, ], }, ], }, ], }, }); Preferences.addSetting({ Loading Loading @@ -113,3 +252,277 @@ Preferences.addSetting({ lazy.TorConnect.quickstart = val; }, }); Preferences.addSetting({ id: "torSettingsReady", _ready: false, setup(emitChange) { if (!lazy.TorSettings.enabled) { // Remain in `false`. return; } // Most likely, TorSettings will already be initialised. if (lazy.TorSettings.initialized) { this._ready = true; return; } // Else, wait for it to be initialised. lazy.TorSettings.initializedPromise.then( () => { this._ready = true; emitChange(); }, error => { // No change in state. console.error("TorSettings failed to initialize.", error); } ); }, get() { return this._ready; }, }); Preferences.addSetting({ id: "torBridgesRaw", deps: ["torSettingsReady"], _value: null, setup(emitChange) { const observer = subject => { const { changes } = subject.wrappedJSObject; // NOTE: We do not include "bridges.lox_id" in the changes. Instead, any // widgets should wait for LoxTopics.UpdateActiveLoxId to ensure that the // Lox module has responded to the change in ID strictly *before* we do. // In particular, we want to make sure the invites and event data has been // cleared. if ( changes.includes("bridges.source") || changes.includes("bridges.bridge_strings") || changes.includes("bridges.builtin_type") ) { // Reset. this._value = null; emitChange(); } }; Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged); return () => { Services.obs.removeObserver( observer, lazy.TorSettingsTopics.SettingsChanged ); }; }, get(_pref, { torSettingsReady }) { if (this._value === null) { if (!torSettingsReady.value) { // TorSettings getter will throw. return null; } const source = lazy.TorSettings.bridges.source; // Cache a value. this._value = { haveBridges: source !== lazy.TorBridgeSource.Invalid, source, builtinType: lazy.TorSettings.bridges.builtin_type, bridgeStrings: lazy.TorSettings.bridges.bridge_strings, }; } return this._value; }, }); Preferences.addSetting({ id: "connectedBridgeId", _value: null, setup(emitChange) { const observer = async () => { // NOTE: It should be safe for this method to be called concurrently. let bridge = null; try { if ( lazy.TorProviderBuilder.currentState() === lazy.TorProviderState.Running ) { bridge = (await lazy.TorProviderBuilder.build()).currentBridge; } // Else, bridge is `null` whilst the provider is not running. } catch (e) { console.warn("Could not get current bridge", e); } const prevVal = this._value; this._value = bridge?.fingerprint ?? null; if (prevVal !== this._value) { emitChange(); } }; Services.obs.addObserver(observer, lazy.TorProviderTopics.BridgeChanged); // NOTE: BridgeChanged is only fired directly by the provider instances, // rather than by TorProviderBuilder. In particular, it will not fire when // the previous provider had a bridge and the new one does not, because // neither provider saw a change in their own bridge. But from the user's // point of view, the overall change would mean that the current bridge has // changed. // Moreover, we want to show no connected bridge whilst we are missing a // provider. Therefore, we also need to listen for a change in provider and // its state. // TODO: Maybe this logic should be moved to TorProviderBuilder itself if it // is ever needed by other parts of the UI. Services.obs.addObserver( observer, lazy.TorProviderTopics.ProviderStateChanged ); // Get the initial value. observer(); return () => { Services.obs.removeObserver( observer, lazy.TorProviderTopics.ProviderStateChanged ); Services.obs.removeObserver( observer, lazy.TorProviderTopics.BridgeChanged ); }; }, get() { return this._value; }, }); Preferences.addSetting({ id: "bridgesEnabled", deps: ["torSettingsReady", "torBridgesRaw"], setup(emitChange) { const observer = subject => { const { changes } = subject.wrappedJSObject; if (changes.includes("bridges.enabled")) { emitChange(); } }; Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged); return () => { Services.obs.removeObserver( observer, lazy.TorSettingsTopics.SettingsChanged ); }; }, get(_prefVal, { torSettingsReady }) { if (!torSettingsReady.value) { // TorSettings.bridges will throw before TorSettings has finished // initialisation. return false; } return lazy.TorSettings.bridges.enabled; }, set(val) { lazy.TorSettings.changeSettings({ bridges: { enabled: val }, }); }, visible({ torSettingsReady }) { return torSettingsReady.value; }, disabled({ torBridgesRaw }) { return !torBridgesRaw.value?.haveBridges; }, }); Preferences.addSetting({ id: "torBridgesDisplay", deps: ["torSettingsReady", "torBridgesRaw", "connectedBridgeId"], getControlConfig(config, { torBridgesRaw, connectedBridgeId }) { config.controlAttrs = { ...config.controlAttrs, // Set the `bridges` and `connnectedBridgeId` object *properties* (rather // than attributes) by using the `.` prefix. ".bridges": torBridgesRaw.value, ".connectedBridgeId": connectedBridgeId.value, }; return config; }, visible({ torSettingsReady }) { return torSettingsReady.value; }, }); Preferences.addSetting({ id: "newBridgesGroup", deps: ["torSettingsReady", "torBridgesRaw"], getControlConfig(config, { torBridgesRaw }) { config.l10nId = torBridgesRaw.value?.haveBridges ? "tor-bridges-replace-bridges-group" : "tor-bridges-add-bridges-group"; return config; }, visible({ torSettingsReady }) { return torSettingsReady.value; }, }); Preferences.addSetting({ id: "builtinBridges", onUserClick() { lazy.openBridgeDialog( window, "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml", null, result => { if (!result.type) { return null; } return lazy.TorSettings.changeSettings({ bridges: { enabled: true, source: lazy.TorBridgeSource.BuiltIn, builtin_type: result.type, }, }); } ); }, }); Preferences.addSetting({ id: "userProvidedBridges", deps: ["torBridgesRaw"], onUserClick(_event, { torBridgesRaw }) { lazy.openUserProvideBridgeDialog( window, torBridgesRaw.value?.haveBridges ? "replace" : "add" ); }, }); Preferences.addSetting({ id: "findMoreBridgesGroup", deps: ["torSettingsReady"], visible({ torSettingsReady }) { return torSettingsReady.value; }, }); Preferences.addSetting({ id: "requestBridges", onUserClick() { lazy.openBridgeDialog( window, "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml", null, result => { if (!result.bridges?.length) { return null; } return lazy.TorSettings.changeSettings({ bridges: { enabled: true, source: lazy.TorBridgeSource.BridgeDB, bridge_strings: result.bridges, }, }); } ); }, }); browser/components/torpreferences/config/helpers.mjs 0 → 100644 +125 −0 Original line number Diff line number Diff line const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs", TorConnectParent: "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs", TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs", }); /** * Force the focus to move to the bridge heading. * * @param {Window} win - The preferences window. * @param {boolean} [forceTopHeading=false] - Force the focus to move to the * top "Bridges" setting heading. */ export function moveFocusToBridgeHeading(win, forceTopHeading = false) { // Move focus to the start of the relevant section, which is a heading. // They have tabindex="-1" so should be focusable, even though they are not // part of the usual tab navigation. // TODO: It might be better if we could use the # named anchor to // re-orient the screen reader position instead of using tabIndex=-1, but // about:preferences currently uses the anchor for showing categories // only. See bugzilla bug 1799153. if ( forceTopHeading || !win.document.getElementById("torBridgesDisplay").focusHeading() ) { win.document .querySelector('setting-group[groupid="torBridges"] moz-fieldset') .focusHeading(); } } /** * Open a bridge dialog that will change the users bridges. * * @param {Window} win - The preferences window. * @param {string} url - The url of the dialog to open. * @param {object?} inputData - The input data to send to the dialog window. * @param {Function} onAccept - The method to call if the bridge dialog was * accepted by the user. This will be passed a "result" object containing * data set by the dialog. This should return a promise that resolves once * the bridge settings have been set, or null if the settings have not * been applied. */ export function openBridgeDialog(win, url, inputData, onAccept) { const result = { accepted: false, connect: false }; let savedSettings = null; win.gSubDialog.open( url, { features: "resizable=yes", closingCallback: () => { if (!result.accepted) { return; } savedSettings = onAccept(result); if (!savedSettings) { // No change in settings. return; } if (!result.connect) { // Do not open about:torconnect. return; } // Wait until the settings are applied before bootstrapping. // NOTE: Saving the settings should also cancel any existing bootstrap // attempt first. See tor-browser#41921. savedSettings.then(() => { // The bridge dialog button is "connect" when Tor is not // bootstrapped, so do the connect. // Start Bootstrapping, which should use the configured bridges. // NOTE: We do this regardless of any previous TorConnect Error. lazy.TorConnectParent.open({ beginBootstrapping: "hard" }); }); }, // closedCallback should be called after gSubDialog has already // re-assigned focus back to the document. closedCallback: () => { if (!savedSettings) { return; } // Wait until the settings have changed, so that the UI could // respond, then move focus. savedSettings.then(() => { moveFocusToBridgeHeading(win); }); }, }, result, inputData ); } /** * Open the user provide dialog. * * @param {Window} win - The preferences window. * @param {string} mode - The mode to open the dialog in: "add", "replace" or * "edit". */ export function openUserProvideBridgeDialog(win, mode) { openBridgeDialog( win, "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml", { mode }, result => { const loxId = result.loxId; if (!loxId && !result.addresses?.length) { return null; } const bridges = { enabled: true }; if (loxId) { bridges.source = lazy.TorBridgeSource.Lox; bridges.lox_id = loxId; } else { bridges.source = lazy.TorBridgeSource.UserProvided; bridges.bridge_strings = result.addresses; } return lazy.TorSettings.changeSettings({ bridges }); } ); } browser/components/torpreferences/content/connectionPane.inc.xhtml +74 −100 Original line number Diff line number Diff line <html:template id="tor-bridges-display-template"> <html:div id="tor-bridges-none" hidden="hidden"> <html:img id="tor-bridges-none-icon" alt="" /> <html:div data-l10n-id="tor-bridges-none-added"></html:div> <html:p data-l10n-id="tor-bridges-none-added"></html:p> </html:div> <html:div id="tor-bridges-current" class="tor-bridges-box" hidden="hidden"> <html:div id="tor-bridges-current-header-bar"> <html:h2 <html:fieldset id="tor-bridges-current" hidden="hidden"> <html:legend> <html:h4 id="tor-bridges-current-heading-non-search" class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-non-search-heading" tabindex="-1" data-l10n-id="tor-bridges-your-bridges" ></html:h2> <!-- Add a duplicate search heading. - In a search result the heading h1.tor-bridges-subcategory-heading - will be hidden, and the h2.tor-bridges-subcategory-heading - will be visible. - As such, all headings below h2.tor-bridges-subcategory-heading also - need to shift one lower in heading level to preseve the correct - hierarchy of - heading levels. - In this case we hide the <h2> heading and show the duplicate <h3> - heading instead. - See tor-browser#43320. --> <html:h3 class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-search-heading" class="tor-bridges-current-heading tor-focusable-heading tor-small-heading" tabindex="-1" data-l10n-id="tor-bridges-your-bridges" ></html:h3> <html:span ></html:h4> </html:legend> <html:p id="tor-bridges-user-label" class="tor-bridges-source-label" data-l10n-id="tor-bridges-source-user" ></html:span> <html:span ></html:p> <html:p id="tor-bridges-built-in-label" class="tor-bridges-source-label" data-l10n-id="tor-bridges-source-built-in" ></html:span> <html:span ></html:p> <html:p id="tor-bridges-requested-label" class="tor-bridges-source-label" data-l10n-id="tor-bridges-source-requested" ></html:span> <html:span id="tor-bridges-lox-label" class="tor-bridges-source-label"> ></html:p> <html:p id="tor-bridges-lox-label" class="tor-bridges-source-label"> <html:img id="tor-bridges-lox-label-icon" alt="" /> <html:span data-l10n-id="tor-bridges-source-lox"></html:span> </html:span> </html:p> <html:button id="tor-bridges-all-options-button" class="tor-bridges-options-button" Loading Loading @@ -77,19 +64,15 @@ data-l10n-id="tor-bridges-menu-item-remove-all-bridges" ></html:panel-item> </html:panel-list> </html:div> <html:div id="tor-bridges-built-in-display" hidden="hidden"> <html:div id="tor-bridges-built-in-type-name"></html:div> <html:div id="tor-bridges-built-in-connected" class="bridge-status-badge" > <html:p id="tor-bridges-built-in-type-name"></html:p> <html:p id="tor-bridges-built-in-connected" class="bridge-status-badge"> <html:div class="bridge-status-icon"></html:div> <html:span data-l10n-id="tor-bridges-built-in-status-connected" ></html:span> </html:div> <html:div id="tor-bridges-built-in-description"></html:div> </html:p> <html:p id="tor-bridges-built-in-description"></html:p> </html:div> <html:div id="tor-bridges-grid-display" Loading Loading @@ -161,19 +144,14 @@ class="tor-bridges-details-box" hidden="hidden" > <html:h3 class="tor-bridges-share-heading tor-small-heading tor-non-search-heading" <html:h5 class="tor-bridges-share-heading tor-small-heading" data-l10n-id="tor-bridges-share-heading" ></html:h3> <!-- Add a duplicate search heading. See tor-browser#43320. --> <html:h4 class="tor-bridges-share-heading tor-small-heading tor-search-heading" data-l10n-id="tor-bridges-share-heading" ></html:h4> <html:span ></html:h5> <html:p id="tor-bridges-share-description" data-l10n-id="tor-bridges-share-description" ></html:span> ></html:p> <html:button id="tor-bridges-copy-addresses-button" data-l10n-id="tor-bridges-copy-addresses-button" Loading @@ -192,15 +170,10 @@ > <html:img alt="" class="tor-bridges-lox-image-inner" /> <html:img alt="" class="tor-bridges-lox-image-outer" /> <html:h3 class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-non-search-heading" tabindex="-1" ></html:h3> <!-- Add a duplicate search heading. See tor-browser#43320. --> <html:h4 class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-search-heading" <html:h5 class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading" tabindex="-1" ></html:h4> ></html:h5> <html:ul class="tor-bridges-lox-list"> <html:li id="tor-bridges-lox-next-unlock-gain-bridges" Loading Loading @@ -270,7 +243,8 @@ ></html:button> </html:div> </html:div> </html:div> </html:fieldset> </html:template> <!-- Tor panel --> Loading Loading
browser/components/preferences/preferences.js +1 −1 Original line number Diff line number Diff line Loading @@ -271,7 +271,7 @@ const CONFIG_PANES = Object.freeze({ connection: { l10nId: "tor-connection-settings-pane", iconSrc: "chrome://browser/content/torconnect/tor-connect.svg", groupIds: ["connectionStatus"], groupIds: ["connectionStatus", "torBridges"], module: "chrome://browser/content/torpreferences/config/connection.mjs", visible: () => { return TorConnect.enabled; Loading
browser/components/preferences/preferences.xhtml +1 −0 Original line number Diff line number Diff line Loading @@ -108,6 +108,7 @@ <script type="module" src="chrome://browser/content/preferences/widgets/update-information.mjs"></script> <script type="module" src="chrome://browser/content/preferences/widgets/update-state.mjs"></script> <script type="module" src="chrome://browser/content/ipprotection/bandwidth-usage.mjs"></script> <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-bridges-display.mjs"></script> <script type="module" src="chrome://browser/content/torpreferences/widgets/tor-connection-status.mjs"></script> <script src="chrome://browser/content/torpreferences/bridgemoji/BridgeEmoji.js"/> </head> Loading
browser/components/torpreferences/config/connection.mjs +413 −0 Original line number Diff line number Diff line Loading @@ -4,11 +4,33 @@ import { Preferences } from "chrome://global/content/preferences/Preferences.mjs const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { InternetStatus: "moz-src:///toolkit/modules/TorConnect.sys.mjs", openBridgeDialog: "chrome://browser/content/torpreferences/config/helpers.mjs", openUserProvideBridgeDialog: "chrome://browser/content/torpreferences/config/helpers.mjs", TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs", TorConnect: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TorConnectStage: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TorConnectTopics: "moz-src:///toolkit/modules/TorConnect.sys.mjs", TorProviderBuilder: "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs", TorProviderState: "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs", TorProviderTopics: "moz-src:///toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs", TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs", TorSettingsTopics: "moz-src:///toolkit/modules/TorSettings.sys.mjs", }); // TODO: Change to GetLoxBridges if Lox enabled, and the account is set up. const TELEGRAM_USER_NAME = "GetBridgesBot"; const TELEGRAM_HREF = `https://t.me/${TELEGRAM_USER_NAME}`; const TOR_BRIDGES_URL_NAME = "bridges.torproject.org"; const TOR_BRIDGES_HREF = "https://bridges.torproject.org"; const TOR_BRIDGES_EMAIL = "bridges@torproject.org"; SettingGroupManager.registerGroups({ connectionStatus: { inProgress: true, Loading Loading @@ -39,6 +61,123 @@ SettingGroupManager.registerGroups({ }, ], }, torBridges: { inProgress: true, l10nId: "tor-bridges-group", supportPage: "tor-manual:bridges", headingLevel: 2, controlAttrs: { "focusable-heading": true }, items: [ { id: "bridgesEnabled", l10nId: "tor-bridges-use-bridges", control: "moz-toggle", }, { id: "torBridgesDisplay", control: "tor-bridges-display", }, { id: "newBridgesGroup", control: "moz-fieldset", controlAttrs: { headinglevel: 3, }, options: [ { control: "moz-box-group", items: [ { id: "builtinBridges", l10nId: "tor-bridges-choose-built-in-button", control: "moz-box-button", }, { id: "userProvidedBridges", l10nId: "tor-bridges-enter-bridges-button", control: "moz-box-button", }, ], }, ], }, { id: "findMoreBridgesGroup", l10nId: "tor-bridges-find-more-group", control: "moz-fieldset", controlAttrs: { headinglevel: 3, }, options: [ { control: "moz-box-item", options: [ { id: "torBridgesRequestBanner", control: "article", options: [ { control: "img", controlAttrs: { alt: "", src: "chrome://browser/content/torpreferences/bridge-bot.svg", }, }, { control: "p", l10nId: "tor-bridges-request-from-browser2", }, { // NOTE: We use the wrapping `div` to simply switch from the // `options` context to the `items` context, with the latter // wrapping the elements in a setting-control. control: "div", items: [ { id: "requestBridges", l10nId: "tor-bridges-request-button2", control: "moz-button", }, ], }, ], }, ], }, { control: "moz-box-group", options: [ { l10nId: "tor-bridges-source-telegram-link", l10nArgs: { telegramUserName: TELEGRAM_USER_NAME }, control: "moz-box-link", iconSrc: "chrome://browser/content/torpreferences/telegram-logo.svg", controlAttrs: { href: TELEGRAM_HREF, }, }, { l10nId: "tor-bridges-source-web-link", l10nArgs: { url: TOR_BRIDGES_URL_NAME }, control: "moz-box-link", iconSrc: "chrome://browser/content/torconnect/network.svg", controlAttrs: { href: TOR_BRIDGES_HREF, }, }, { l10nId: "tor-bridges-source-email-link", l10nArgs: { address: TOR_BRIDGES_EMAIL }, control: "moz-box-item", iconSrc: "chrome://browser/content/torpreferences/mail.svg", }, ], }, ], }, ], }, }); Preferences.addSetting({ Loading Loading @@ -113,3 +252,277 @@ Preferences.addSetting({ lazy.TorConnect.quickstart = val; }, }); Preferences.addSetting({ id: "torSettingsReady", _ready: false, setup(emitChange) { if (!lazy.TorSettings.enabled) { // Remain in `false`. return; } // Most likely, TorSettings will already be initialised. if (lazy.TorSettings.initialized) { this._ready = true; return; } // Else, wait for it to be initialised. lazy.TorSettings.initializedPromise.then( () => { this._ready = true; emitChange(); }, error => { // No change in state. console.error("TorSettings failed to initialize.", error); } ); }, get() { return this._ready; }, }); Preferences.addSetting({ id: "torBridgesRaw", deps: ["torSettingsReady"], _value: null, setup(emitChange) { const observer = subject => { const { changes } = subject.wrappedJSObject; // NOTE: We do not include "bridges.lox_id" in the changes. Instead, any // widgets should wait for LoxTopics.UpdateActiveLoxId to ensure that the // Lox module has responded to the change in ID strictly *before* we do. // In particular, we want to make sure the invites and event data has been // cleared. if ( changes.includes("bridges.source") || changes.includes("bridges.bridge_strings") || changes.includes("bridges.builtin_type") ) { // Reset. this._value = null; emitChange(); } }; Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged); return () => { Services.obs.removeObserver( observer, lazy.TorSettingsTopics.SettingsChanged ); }; }, get(_pref, { torSettingsReady }) { if (this._value === null) { if (!torSettingsReady.value) { // TorSettings getter will throw. return null; } const source = lazy.TorSettings.bridges.source; // Cache a value. this._value = { haveBridges: source !== lazy.TorBridgeSource.Invalid, source, builtinType: lazy.TorSettings.bridges.builtin_type, bridgeStrings: lazy.TorSettings.bridges.bridge_strings, }; } return this._value; }, }); Preferences.addSetting({ id: "connectedBridgeId", _value: null, setup(emitChange) { const observer = async () => { // NOTE: It should be safe for this method to be called concurrently. let bridge = null; try { if ( lazy.TorProviderBuilder.currentState() === lazy.TorProviderState.Running ) { bridge = (await lazy.TorProviderBuilder.build()).currentBridge; } // Else, bridge is `null` whilst the provider is not running. } catch (e) { console.warn("Could not get current bridge", e); } const prevVal = this._value; this._value = bridge?.fingerprint ?? null; if (prevVal !== this._value) { emitChange(); } }; Services.obs.addObserver(observer, lazy.TorProviderTopics.BridgeChanged); // NOTE: BridgeChanged is only fired directly by the provider instances, // rather than by TorProviderBuilder. In particular, it will not fire when // the previous provider had a bridge and the new one does not, because // neither provider saw a change in their own bridge. But from the user's // point of view, the overall change would mean that the current bridge has // changed. // Moreover, we want to show no connected bridge whilst we are missing a // provider. Therefore, we also need to listen for a change in provider and // its state. // TODO: Maybe this logic should be moved to TorProviderBuilder itself if it // is ever needed by other parts of the UI. Services.obs.addObserver( observer, lazy.TorProviderTopics.ProviderStateChanged ); // Get the initial value. observer(); return () => { Services.obs.removeObserver( observer, lazy.TorProviderTopics.ProviderStateChanged ); Services.obs.removeObserver( observer, lazy.TorProviderTopics.BridgeChanged ); }; }, get() { return this._value; }, }); Preferences.addSetting({ id: "bridgesEnabled", deps: ["torSettingsReady", "torBridgesRaw"], setup(emitChange) { const observer = subject => { const { changes } = subject.wrappedJSObject; if (changes.includes("bridges.enabled")) { emitChange(); } }; Services.obs.addObserver(observer, lazy.TorSettingsTopics.SettingsChanged); return () => { Services.obs.removeObserver( observer, lazy.TorSettingsTopics.SettingsChanged ); }; }, get(_prefVal, { torSettingsReady }) { if (!torSettingsReady.value) { // TorSettings.bridges will throw before TorSettings has finished // initialisation. return false; } return lazy.TorSettings.bridges.enabled; }, set(val) { lazy.TorSettings.changeSettings({ bridges: { enabled: val }, }); }, visible({ torSettingsReady }) { return torSettingsReady.value; }, disabled({ torBridgesRaw }) { return !torBridgesRaw.value?.haveBridges; }, }); Preferences.addSetting({ id: "torBridgesDisplay", deps: ["torSettingsReady", "torBridgesRaw", "connectedBridgeId"], getControlConfig(config, { torBridgesRaw, connectedBridgeId }) { config.controlAttrs = { ...config.controlAttrs, // Set the `bridges` and `connnectedBridgeId` object *properties* (rather // than attributes) by using the `.` prefix. ".bridges": torBridgesRaw.value, ".connectedBridgeId": connectedBridgeId.value, }; return config; }, visible({ torSettingsReady }) { return torSettingsReady.value; }, }); Preferences.addSetting({ id: "newBridgesGroup", deps: ["torSettingsReady", "torBridgesRaw"], getControlConfig(config, { torBridgesRaw }) { config.l10nId = torBridgesRaw.value?.haveBridges ? "tor-bridges-replace-bridges-group" : "tor-bridges-add-bridges-group"; return config; }, visible({ torSettingsReady }) { return torSettingsReady.value; }, }); Preferences.addSetting({ id: "builtinBridges", onUserClick() { lazy.openBridgeDialog( window, "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml", null, result => { if (!result.type) { return null; } return lazy.TorSettings.changeSettings({ bridges: { enabled: true, source: lazy.TorBridgeSource.BuiltIn, builtin_type: result.type, }, }); } ); }, }); Preferences.addSetting({ id: "userProvidedBridges", deps: ["torBridgesRaw"], onUserClick(_event, { torBridgesRaw }) { lazy.openUserProvideBridgeDialog( window, torBridgesRaw.value?.haveBridges ? "replace" : "add" ); }, }); Preferences.addSetting({ id: "findMoreBridgesGroup", deps: ["torSettingsReady"], visible({ torSettingsReady }) { return torSettingsReady.value; }, }); Preferences.addSetting({ id: "requestBridges", onUserClick() { lazy.openBridgeDialog( window, "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml", null, result => { if (!result.bridges?.length) { return null; } return lazy.TorSettings.changeSettings({ bridges: { enabled: true, source: lazy.TorBridgeSource.BridgeDB, bridge_strings: result.bridges, }, }); } ); }, });
browser/components/torpreferences/config/helpers.mjs 0 → 100644 +125 −0 Original line number Diff line number Diff line const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { TorBridgeSource: "moz-src:///toolkit/modules/TorSettings.sys.mjs", TorConnectParent: "moz-src:///browser/components/torconnect/TorConnectParent.sys.mjs", TorSettings: "moz-src:///toolkit/modules/TorSettings.sys.mjs", }); /** * Force the focus to move to the bridge heading. * * @param {Window} win - The preferences window. * @param {boolean} [forceTopHeading=false] - Force the focus to move to the * top "Bridges" setting heading. */ export function moveFocusToBridgeHeading(win, forceTopHeading = false) { // Move focus to the start of the relevant section, which is a heading. // They have tabindex="-1" so should be focusable, even though they are not // part of the usual tab navigation. // TODO: It might be better if we could use the # named anchor to // re-orient the screen reader position instead of using tabIndex=-1, but // about:preferences currently uses the anchor for showing categories // only. See bugzilla bug 1799153. if ( forceTopHeading || !win.document.getElementById("torBridgesDisplay").focusHeading() ) { win.document .querySelector('setting-group[groupid="torBridges"] moz-fieldset') .focusHeading(); } } /** * Open a bridge dialog that will change the users bridges. * * @param {Window} win - The preferences window. * @param {string} url - The url of the dialog to open. * @param {object?} inputData - The input data to send to the dialog window. * @param {Function} onAccept - The method to call if the bridge dialog was * accepted by the user. This will be passed a "result" object containing * data set by the dialog. This should return a promise that resolves once * the bridge settings have been set, or null if the settings have not * been applied. */ export function openBridgeDialog(win, url, inputData, onAccept) { const result = { accepted: false, connect: false }; let savedSettings = null; win.gSubDialog.open( url, { features: "resizable=yes", closingCallback: () => { if (!result.accepted) { return; } savedSettings = onAccept(result); if (!savedSettings) { // No change in settings. return; } if (!result.connect) { // Do not open about:torconnect. return; } // Wait until the settings are applied before bootstrapping. // NOTE: Saving the settings should also cancel any existing bootstrap // attempt first. See tor-browser#41921. savedSettings.then(() => { // The bridge dialog button is "connect" when Tor is not // bootstrapped, so do the connect. // Start Bootstrapping, which should use the configured bridges. // NOTE: We do this regardless of any previous TorConnect Error. lazy.TorConnectParent.open({ beginBootstrapping: "hard" }); }); }, // closedCallback should be called after gSubDialog has already // re-assigned focus back to the document. closedCallback: () => { if (!savedSettings) { return; } // Wait until the settings have changed, so that the UI could // respond, then move focus. savedSettings.then(() => { moveFocusToBridgeHeading(win); }); }, }, result, inputData ); } /** * Open the user provide dialog. * * @param {Window} win - The preferences window. * @param {string} mode - The mode to open the dialog in: "add", "replace" or * "edit". */ export function openUserProvideBridgeDialog(win, mode) { openBridgeDialog( win, "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml", { mode }, result => { const loxId = result.loxId; if (!loxId && !result.addresses?.length) { return null; } const bridges = { enabled: true }; if (loxId) { bridges.source = lazy.TorBridgeSource.Lox; bridges.lox_id = loxId; } else { bridges.source = lazy.TorBridgeSource.UserProvided; bridges.bridge_strings = result.addresses; } return lazy.TorSettings.changeSettings({ bridges }); } ); }
browser/components/torpreferences/content/connectionPane.inc.xhtml +74 −100 Original line number Diff line number Diff line <html:template id="tor-bridges-display-template"> <html:div id="tor-bridges-none" hidden="hidden"> <html:img id="tor-bridges-none-icon" alt="" /> <html:div data-l10n-id="tor-bridges-none-added"></html:div> <html:p data-l10n-id="tor-bridges-none-added"></html:p> </html:div> <html:div id="tor-bridges-current" class="tor-bridges-box" hidden="hidden"> <html:div id="tor-bridges-current-header-bar"> <html:h2 <html:fieldset id="tor-bridges-current" hidden="hidden"> <html:legend> <html:h4 id="tor-bridges-current-heading-non-search" class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-non-search-heading" tabindex="-1" data-l10n-id="tor-bridges-your-bridges" ></html:h2> <!-- Add a duplicate search heading. - In a search result the heading h1.tor-bridges-subcategory-heading - will be hidden, and the h2.tor-bridges-subcategory-heading - will be visible. - As such, all headings below h2.tor-bridges-subcategory-heading also - need to shift one lower in heading level to preseve the correct - hierarchy of - heading levels. - In this case we hide the <h2> heading and show the duplicate <h3> - heading instead. - See tor-browser#43320. --> <html:h3 class="tor-bridges-current-heading tor-focusable-heading tor-small-heading tor-search-heading" class="tor-bridges-current-heading tor-focusable-heading tor-small-heading" tabindex="-1" data-l10n-id="tor-bridges-your-bridges" ></html:h3> <html:span ></html:h4> </html:legend> <html:p id="tor-bridges-user-label" class="tor-bridges-source-label" data-l10n-id="tor-bridges-source-user" ></html:span> <html:span ></html:p> <html:p id="tor-bridges-built-in-label" class="tor-bridges-source-label" data-l10n-id="tor-bridges-source-built-in" ></html:span> <html:span ></html:p> <html:p id="tor-bridges-requested-label" class="tor-bridges-source-label" data-l10n-id="tor-bridges-source-requested" ></html:span> <html:span id="tor-bridges-lox-label" class="tor-bridges-source-label"> ></html:p> <html:p id="tor-bridges-lox-label" class="tor-bridges-source-label"> <html:img id="tor-bridges-lox-label-icon" alt="" /> <html:span data-l10n-id="tor-bridges-source-lox"></html:span> </html:span> </html:p> <html:button id="tor-bridges-all-options-button" class="tor-bridges-options-button" Loading Loading @@ -77,19 +64,15 @@ data-l10n-id="tor-bridges-menu-item-remove-all-bridges" ></html:panel-item> </html:panel-list> </html:div> <html:div id="tor-bridges-built-in-display" hidden="hidden"> <html:div id="tor-bridges-built-in-type-name"></html:div> <html:div id="tor-bridges-built-in-connected" class="bridge-status-badge" > <html:p id="tor-bridges-built-in-type-name"></html:p> <html:p id="tor-bridges-built-in-connected" class="bridge-status-badge"> <html:div class="bridge-status-icon"></html:div> <html:span data-l10n-id="tor-bridges-built-in-status-connected" ></html:span> </html:div> <html:div id="tor-bridges-built-in-description"></html:div> </html:p> <html:p id="tor-bridges-built-in-description"></html:p> </html:div> <html:div id="tor-bridges-grid-display" Loading Loading @@ -161,19 +144,14 @@ class="tor-bridges-details-box" hidden="hidden" > <html:h3 class="tor-bridges-share-heading tor-small-heading tor-non-search-heading" <html:h5 class="tor-bridges-share-heading tor-small-heading" data-l10n-id="tor-bridges-share-heading" ></html:h3> <!-- Add a duplicate search heading. See tor-browser#43320. --> <html:h4 class="tor-bridges-share-heading tor-small-heading tor-search-heading" data-l10n-id="tor-bridges-share-heading" ></html:h4> <html:span ></html:h5> <html:p id="tor-bridges-share-description" data-l10n-id="tor-bridges-share-description" ></html:span> ></html:p> <html:button id="tor-bridges-copy-addresses-button" data-l10n-id="tor-bridges-copy-addresses-button" Loading @@ -192,15 +170,10 @@ > <html:img alt="" class="tor-bridges-lox-image-inner" /> <html:img alt="" class="tor-bridges-lox-image-outer" /> <html:h3 class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-non-search-heading" tabindex="-1" ></html:h3> <!-- Add a duplicate search heading. See tor-browser#43320. --> <html:h4 class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading tor-search-heading" <html:h5 class="tor-bridges-lox-next-unlock-counter tor-small-heading tor-bridges-lox-intro tor-focusable-heading" tabindex="-1" ></html:h4> ></html:h5> <html:ul class="tor-bridges-lox-list"> <html:li id="tor-bridges-lox-next-unlock-gain-bridges" Loading Loading @@ -270,7 +243,8 @@ ></html:button> </html:div> </html:div> </html:div> </html:fieldset> </html:template> <!-- Tor panel --> Loading