Pier Angelo Vendrame pushed to branch tor-browser-128.7.0esr-14.5-1 at The Tor Project / Applications / Tor Browser
Commits: 6c4f2b1c by Henry Wilkes at 2025-02-10T16:10:58+00:00 fixup! TB 41435: Add a Tor Browser migration function
TB 43462: Drop preference for blocking internet tests since it no longer requires Moat access.
- - - - - 3e08adbf by Henry Wilkes at 2025-02-10T16:10:59+00:00 fixup! TB 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
TB 43462: Make the internet status in `about:preferences` update automatically without any user input.
We listen to the status via TorConnect.
- - - - - f3a86166 by Henry Wilkes at 2025-02-10T16:10:59+00:00 fixup! Tor Browser strings
TB 43462: Add a string for the "unknown" internet status.
- - - - - b97265e0 by Henry Wilkes at 2025-02-10T16:12:36+00:00 fixup! TB 40597: Implement TorSettings module
TB 43462: Use NetworkLinkService instead of Moat for the internet test.
TorConnect now exposes an `internetStatus` property, which is kept up to date using `NetworkLinkService`.
- - - - -
7 changed files:
- browser/components/BrowserGlue.sys.mjs - browser/components/torpreferences/content/connectionPane.js - browser/components/torpreferences/content/connectionPane.xhtml - browser/components/torpreferences/content/torPreferences.css - toolkit/locales/en-US/toolkit/global/tor-browser.ftl - toolkit/modules/Moat.sys.mjs - toolkit/modules/TorConnect.sys.mjs
Changes:
===================================== browser/components/BrowserGlue.sys.mjs ===================================== @@ -4824,6 +4824,7 @@ BrowserGlue.prototype = { // since we hid the UI (tor-browser#43118). // Version 6: Tor Browser 14.5a3: Clear preference for TorSettings that is // no longer used (tor-browser#41921). + // Drop unused TorConnect setting (tor-browser#43462). const TBB_MIGRATION_VERSION = 6; const MIGRATION_PREF = "torbrowser.migration.version";
@@ -4908,6 +4909,7 @@ BrowserGlue.prototype = {
if (currentVersion < 6) { Services.prefs.clearUserPref("torbrowser.settings.enabled"); + Services.prefs.clearUserPref("torbrowser.bootstrap.allow_internet_test"); }
Services.prefs.setIntPref(MIGRATION_PREF, TBB_MIGRATION_VERSION);
===================================== browser/components/torpreferences/content/connectionPane.js ===================================== @@ -22,13 +22,9 @@ const { TorProviderBuilder, TorProviderTopics } = ChromeUtils.importESModule( "resource://gre/modules/TorProviderBuilder.sys.mjs" );
-const { TorConnect, TorConnectTopics, TorConnectStage, TorCensorshipLevel } = +const { InternetStatus, TorConnect, TorConnectTopics, TorConnectStage } = ChromeUtils.importESModule("resource://gre/modules/TorConnect.sys.mjs");
-const { MoatRPC } = ChromeUtils.importESModule( - "resource://gre/modules/Moat.sys.mjs" -); - const { QRCode } = ChromeUtils.importESModule( "resource://gre/modules/QRCode.sys.mjs" ); @@ -2371,12 +2367,6 @@ const gNetworkStatus = { this._internetResultEl = this._internetAreaEl.querySelector( ".network-status-result" ); - this._internetTestButton = document.getElementById( - "network-status-internet-test-button" - ); - this._internetTestButton.addEventListener("click", () => { - this._startInternetTest(); - });
this._torAreaEl = document.getElementById("network-status-tor-area"); this._torResultEl = this._torAreaEl.querySelector(".network-status-result"); @@ -2387,10 +2377,11 @@ const gNetworkStatus = { TorConnect.openTorConnect({ beginBootstrapping: "soft" }); });
- this._updateInternetStatus("unknown"); + this._updateInternetStatus(); this._updateTorConnectionStatus();
Services.obs.addObserver(this, TorConnectTopics.StageChange); + Services.obs.addObserver(this, TorConnectTopics.InternetStatusChange); },
/** @@ -2398,98 +2389,42 @@ const gNetworkStatus = { */ 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: { + case TorConnectTopics.StageChange: this._updateTorConnectionStatus(); break; - } - } - }, - - /** - * Whether the test should be disabled. - * - * @type {boolean} - */ - _internetTestDisabled: false, - /** - * Start the internet test. - */ - async _startInternetTest() { - if (this._internetTestDisabled) { - return; - } - this._internetTestDisabled = true; - // We use "aria-disabled" rather than the "disabled" attribute so that the - // button can remain focusable during the test. - // TODO: Replace with moz-button when it handles this for us. See - // tor-browser#43275. - this._internetTestButton.setAttribute("aria-disabled", "true"); - this._internetTestButton.classList.add("spoof-button-disabled"); - this._internetTestButton.tabIndex = -1; - try { - this._updateInternetStatus("testing"); - const mrpc = new MoatRPC(); - let status = null; - try { - await mrpc.init(); - status = await mrpc.testInternetConnection(); - } catch (err) { - log.error("Error while checking the Internet connection", err); - } finally { - mrpc.uninit(); - } - if (status) { - this._updateInternetStatus(status.successful ? "online" : "offline"); - } else { - this._updateInternetStatus("unknown"); - } - } finally { - this._internetTestButton.removeAttribute("aria-disabled"); - this._internetTestButton.classList.remove("spoof-button-disabled"); - this._internetTestButton.tabIndex = 0; - this._internetTestDisabled = false; + case TorConnectTopics.InternetStatusChange: + this._updateInternetStatus(); + break; } },
/** * Update the shown internet status. - * - * @param {string} stateName - The name of the state to show. */ - _updateInternetStatus(stateName) { + _updateInternetStatus() { let l10nId; - switch (stateName) { - case "testing": - l10nId = "tor-connection-internet-status-testing"; - break; - case "offline": + let isOffline = false; + switch (TorConnect.internetStatus) { + case InternetStatus.Offline: l10nId = "tor-connection-internet-status-offline"; + isOffline = true; break; - case "online": + case InternetStatus.Online: l10nId = "tor-connection-internet-status-online"; break; + default: + l10nId = "tor-connection-internet-status-unknown"; + break; } - if (l10nId) { - this._internetResultEl.setAttribute("data-l10n-id", l10nId); - } else { - this._internetResultEl.removeAttribute("data-l10n-id"); - this._internetResultEl.textContent = ""; - } - - this._internetAreaEl.classList.toggle( - "status-loading", - stateName === "testing" - ); - this._internetAreaEl.classList.toggle( - "status-offline", - stateName === "offline" - ); + this._internetResultEl.setAttribute("data-l10n-id", l10nId); + this._internetAreaEl.classList.toggle("status-offline", isOffline); },
/**
===================================== browser/components/torpreferences/content/connectionPane.xhtml ===================================== @@ -31,27 +31,15 @@ aria-labelledby="network-status-internet-area-label" > <html:img alt="" class="network-status-icon" /> - <!-- Use an aria-live area to announce "Internet: Online" or - - "Internet: Offline". We only expect this to change when triggered by - - the user clicking the "Test" button, so shouldn't occur unexpectedly. - --> - <html:div - class="network-status-live-area" - aria-live="polite" - aria-atomic="true" - > - <html:span - id="network-status-internet-area-label" - class="network-status-label" - data-l10n-id="tor-connection-internet-status-label" - ></html:span> - <img alt="" class="network-status-loading-icon tor-loading-icon" /> - <html:span class="network-status-result"></html:span> - </html:div> - <html:button - id="network-status-internet-test-button" - data-l10n-id="tor-connection-internet-status-test-button" - ></html:button> + <!-- 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" @@ -60,8 +48,8 @@ aria-labelledby="network-status-tor-area-label" > <html:img alt="" class="network-status-icon" /> - <!-- NOTE: Unlike #network-status-internet-area, we do not wrap the label - - and status ("Tor network: Not connected", etc) in an aria-live area. + <!-- 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. -->
===================================== browser/components/torpreferences/content/torPreferences.css ===================================== @@ -90,24 +90,12 @@ button.spoof-button-disabled { } }
-.network-status-live-area { - display: contents; -} - .network-status-label { font-weight: bold; margin-inline-end: 0.75em; }
-.network-status-loading-icon { - margin-inline-end: 0.5em; -} - -#network-status-internet-area:not(.status-loading) .network-status-loading-icon { - display: none; -} - -.network-status-result:not(:empty) { +.network-status-result { margin-inline-end: 0.75em; }
===================================== toolkit/locales/en-US/toolkit/global/tor-browser.ftl ===================================== @@ -65,19 +65,15 @@ tor-connection-quickstart-checkbox = # Prefix before the internet connection status. # "Internet" is not a proper noun, but is capitalized because it is the start of a sentence. tor-connection-internet-status-label = Internet: -# Button to test the internet connection. -# Here "Test" is a verb, as in "test the internet connection". -# Uses sentence case in English (US). -tor-connection-internet-status-test-button = Test -# Shown when testing the internet status. -# Uses sentence case in English (US). -tor-connection-internet-status-testing = Testing… # Shown when the user is connected to the internet. # Uses sentence case in English (US). tor-connection-internet-status-online = Online # Shown when the user is not connected to the internet. # Uses sentence case in English (US). tor-connection-internet-status-offline = Offline +# Shown when the user has an unknown internet connection status. +# Uses sentence case in English (US). +tor-connection-internet-status-unknown = Unknown status
# Prefix before the Tor network connection status. # Uses sentence case in English (US).
===================================== toolkit/modules/Moat.sys.mjs ===================================== @@ -23,57 +23,6 @@ const TorLauncherPrefs = Object.freeze({ moat_service: "extensions.torlauncher.moat_service", });
-/** - * A special response listener that collects the received headers. - */ -class InternetTestResponseListener { - #promise; - #resolve; - #reject; - constructor() { - this.#promise = new Promise((resolve, reject) => { - this.#resolve = resolve; - this.#reject = reject; - }); - } - - // callers wait on this for final response - get status() { - return this.#promise; - } - - onStartRequest() {} - - // resolve or reject our Promise - onStopRequest(request, status) { - try { - const statuses = { - components: status, - successful: Components.isSuccessCode(status), - }; - try { - if (statuses.successful) { - statuses.http = request.responseStatus; - statuses.date = request.getResponseHeader("Date"); - } - } catch (err) { - console.warn( - "Successful request, but could not get the HTTP status or date", - err - ); - } - this.#resolve(statuses); - } catch (err) { - this.#reject(err); - } - } - - onDataAvailable() { - // We do not care of the actual data, as long as we have a successful - // connection - } -} - /** * @typedef {Object} MoatBridges * @@ -184,18 +133,6 @@ export class MoatRPC { return { response, cancelled }; }
- async testInternetConnection() { - const uri = `${Services.prefs.getStringPref( - TorLauncherPrefs.moat_service - )}/circumvention/countries`; - const ch = this.#requestBuilder.buildHttpHandler(uri); - ch.requestMethod = "HEAD"; - - const listener = new InternetTestResponseListener(); - ch.asyncOpen(listener, ch); - return listener.status; - } - /** * Request a CAPTCHA challenge. *
===================================== toolkit/modules/TorConnect.sys.mjs ===================================== @@ -16,9 +16,16 @@ ChromeUtils.defineESModuleGetters(lazy, { TorSettingsTopics: "resource://gre/modules/TorSettings.sys.mjs", });
+ChromeUtils.defineLazyGetter(lazy, "NetworkLinkService", () => { + return Cc["@mozilla.org/network/network-link-service;1"].getService( + Ci.nsINetworkLinkService + ); +}); + +const NETWORK_LINK_TOPIC = "network:link-status-changed"; + const TorConnectPrefs = Object.freeze({ censorship_level: "torbrowser.debug.censorship_level", - allow_internet_test: "torbrowser.bootstrap.allow_internet_test", log_level: "torbrowser.bootstrap.log_level", /* prompt_at_startup now controls whether the quickstart can trigger. */ prompt_at_startup: "extensions.torlauncher.prompt_at_startup", @@ -82,6 +89,7 @@ export const TorConnectTopics = Object.freeze({ // TODO: Remove torconnect:state-change when pages have switched to stage. StateChange: "torconnect:state-change", QuickstartChange: "torconnect:quickstart-change", + InternetStatusChange: "torconnect:internet-status-change", BootstrapProgress: "torconnect:bootstrap-progress", BootstrapComplete: "torconnect:bootstrap-complete", // TODO: Remove torconnect:error when pages have switched to stage. @@ -108,10 +116,6 @@ export const TorConnectTopics = Object.freeze({ * should be an Array of MoatBridges objects that match the bridge settings * accepted by TorSettings.bridges, plus you may add a "simulateCensorship" * property to make only their bootstrap attempts fail. - * @property {boolean} [options.testInternet] - Whether to also test the - * internet connection. - * @property {boolean} [options.simulateOffline] - Whether to simulate an - * offline test result. This will not cause the bootstrap to fail. * @property {string} [options.regionCode] - The region code to use to fetch * auto-bootstrap settings, or "automatic" to automatically choose the region. */ @@ -141,18 +145,6 @@ class BootstrapAttempt { * @type {?TorBootstrapRequest} */ #bootstrap = null; - /** - * The error returned by the bootstrap request, if any. - * - * @type {?Error} - */ - #bootstrapError = null; - /** - * The ongoing internet test, if any. - * - * @type {?InternetTest} - */ - #internetTest = null; /** * The method to call to complete the `run` promise. * @@ -192,13 +184,6 @@ class BootstrapAttempt { return; } this.#resolved = true; - try { - // Should be ok to call this twice in the case where we "cancel" the - // bootstrap. - this.#internetTest?.cancel(); - } catch (error) { - lazy.logger.error("Unexpected error in bootstrap cleanup", error); - } if (arg.error) { reject(arg.error); } else { @@ -259,35 +244,7 @@ class BootstrapAttempt { this.#resolveRun({ result: "complete" }); }; this.#bootstrap.onbootstraperror = error => { - if (this.#bootstrapError) { - lazy.logger.warn("Another bootstrap error", error); - return; - } - // We have to wait for the Internet test to finish before sending the - // bootstrap error - this.#bootstrapError = error; - this.#maybeTransitionToError(); - }; - if (options.testInternet) { - this.#internetTest = new InternetTest(options.simulateOffline); - this.#internetTest.onResult = () => { - this.#maybeTransitionToError(); - }; - this.#internetTest.onError = () => { - this.#maybeTransitionToError(); - }; - } - - this.#bootstrap.bootstrap(); - } - - /** - * Callback for when we get a new bootstrap error or a change in the internet - * status. - */ - #maybeTransitionToError() { - if (this.#resolved || this.#cancelled) { - if (this.#bootstrapError) { + if (this.#resolved || this.#cancelled) { // We ignore this error since it occurred after cancelling (by the // user), or we have already resolved. We assume the error is just a // side effect of the cancelling. @@ -295,45 +252,16 @@ class BootstrapAttempt { // "Building circuits: Establishing a Tor circuit failed". // TODO: Maybe move this logic deeper in the process to know when to // filter out such errors triggered by cancelling. - lazy.logger.warn("Post-complete error.", this.#bootstrapError); + lazy.logger.warn("Post-complete bootstrap error.", error); + return; } - return; - }
- if ( - this.#internetTest && - this.#internetTest.status === InternetStatus.Unknown && - this.#internetTest.error === null && - this.#internetTest.enabled - ) { - // We have been called by a failed bootstrap, but the internet test has - // not run yet - force it to run immediately! - this.#internetTest.test(); - // Return from this call, because the Internet test's callback will call - // us again. - return; - } - // Do not transition to "offline" until we are sure that also the bootstrap - // failed, in case Moat is down but the bootstrap can proceed anyway. - if (!this.#bootstrapError) { - return; - } - if (this.#internetTest?.status === InternetStatus.Offline) { - if (this.#bootstrapError) { - lazy.logger.info( - "Ignoring bootstrap error since offline.", - this.#bootstrapError - ); - } - this.#resolveRun({ result: "offline" }); - return; - } - this.#resolveRun({ - error: new TorConnectError( - TorConnectError.BootstrapError, - this.#bootstrapError - ), - }); + this.#resolveRun({ + error: new TorConnectError(TorConnectError.BootstrapError, error), + }); + }; + + this.#bootstrap.bootstrap(); }
/** @@ -355,7 +283,6 @@ class BootstrapAttempt { // cancelled. In particular, there is a small chance that the bootstrap // completes, in which case we want to be able to resolve with a success // instead. - this.#internetTest?.cancel(); await this.#bootstrap?.cancel(); this.#resolveRun({ result: "cancelled" }); } @@ -729,117 +656,6 @@ export const InternetStatus = Object.freeze({ Online: 1, });
-class InternetTest { - #enabled; - #status = InternetStatus.Unknown; - #error = null; - #pending = false; - #canceled = false; - #timeout = 0; - #simulateOffline = false; - - constructor(simulateOffline) { - this.#simulateOffline = simulateOffline; - - this.#enabled = Services.prefs.getBoolPref( - TorConnectPrefs.allow_internet_test, - true - ); - if (this.#enabled) { - this.#timeout = setTimeout(() => { - this.#timeout = 0; - this.test(); - }, this.#timeoutRand()); - } - this.onResult = _online => {}; - this.onError = _error => {}; - } - - /** - * Perform the internet test. - * - * While this is an async method, the callers are not expected to await it, - * as we are also using callbacks. - */ - async test() { - if (this.#pending || !this.#enabled) { - return; - } - this.cancel(); - this.#pending = true; - this.#canceled = false; - - lazy.logger.info("Starting the Internet test"); - - if (this.#simulateOffline) { - await new Promise(res => setTimeout(res, 500)); - - this.#status = InternetStatus.Offline; - - if (this.#canceled) { - return; - } - this.onResult(this.#status); - return; - } - - const mrpc = new lazy.MoatRPC(); - try { - await mrpc.init(); - const status = await mrpc.testInternetConnection(); - this.#status = status.successful - ? InternetStatus.Online - : InternetStatus.Offline; - // TODO: We could consume the date we got from the HTTP request to detect - // big clock skews that might prevent a successfull bootstrap. - lazy.logger.info(`Performed Internet test, outcome ${this.#status}`); - } catch (err) { - lazy.logger.error("Error while checking the Internet connection", err); - this.#error = err; - this.#pending = false; - } finally { - mrpc.uninit(); - } - - if (this.#canceled) { - return; - } - if (this.#error) { - this.onError(this.#error); - } else { - this.onResult(this.#status); - } - } - - cancel() { - this.#canceled = true; - if (this.#timeout) { - clearTimeout(this.#timeout); - this.#timeout = 0; - } - } - - get status() { - return this.#status; - } - - get error() { - return this.#error; - } - - get enabled() { - return this.#enabled; - } - - // We randomize the Internet test timeout to make fingerprinting it harder, at - // least a little bit... - #timeoutRand() { - const offset = 30000; - const randRange = 5000; - return offset + randRange * (Math.random() * 2 - 1); - } -} - export const TorConnectStage = Object.freeze({ Disabled: "Disabled", Loading: "Loading", @@ -903,6 +719,13 @@ export const TorConnect = { */ simulateBootstrapOptions: {},
+ /** + * Whether to simulate being offline. + * + * @type {boolean} + */ + simulateOffline: false, + /** * The name of the current stage the user is in. * @@ -1004,10 +827,6 @@ export const TorConnect = { })() ),
- // This is used as a helper to make the state of about:torconnect persistent - // during a session, but TorConnect does not use this data at all. - _uiState: {}, - /** * The status of the most recent bootstrap attempt. * @@ -1029,6 +848,36 @@ export const TorConnect = { ); },
+ /** + * The current internet status. One of the InternetStatus values. + * + * @type {integer} + */ + _internetStatus: InternetStatus.Unknown, + + get internetStatus() { + return this._internetStatus; + }, + + /** + * Update the _internetStatus value. + */ + _updateInternetStatus() { + let newStatus; + if (lazy.NetworkLinkService.linkStatusKnown) { + newStatus = lazy.NetworkLinkService.isLinkUp + ? InternetStatus.Online + : InternetStatus.Offline; + } else { + newStatus = InternetStatus.Unknown; + } + if (newStatus === this._internetStatus) { + return; + } + this._internetStatus = newStatus; + Services.obs.notifyObservers(null, TorConnectTopics.InternetStatusChange); + }, + // init should be called by TorStartupService init() { lazy.logger.debug("TorConnect.init()"); @@ -1048,6 +897,9 @@ export const TorConnect = { observeTopic(lazy.TorProviderTopics.ProcessExited); observeTopic(lazy.TorProviderTopics.HasWarnOrErr); observeTopic(lazy.TorSettingsTopics.SettingsChanged); + observeTopic(NETWORK_LINK_TOPIC); + + this._updateInternetStatus();
// NOTE: At this point, _requestedStage should still be `null`. this._setStage(TorConnectStage.Start); @@ -1109,6 +961,9 @@ export const TorConnect = { this._makeStageRequest(TorConnectStage.Start); } break; + case NETWORK_LINK_TOPIC: + this._updateInternetStatus(); + break; } },
@@ -1330,9 +1185,6 @@ export const TorConnect = { bootstrapOptions.simulateDelay = this.simulateBootstrapOptions.simulateDelay; } - if (this.simulateBootstrapOptions.simulateOffline) { - bootstrapOptions.simulateOffline = true; - } if (this.simulateBootstrapOptions.simulateMoatResponse) { bootstrapOptions.simulateMoatResponse = this.simulateBootstrapOptions.simulateMoatResponse; @@ -1446,12 +1298,6 @@ export const TorConnect = { ? new AutoBootstrapAttempt() : new BootstrapAttempt();
- if (!regionCode) { - // Only test internet for the first bootstrap attempt. - // TODO: Remove this since we do not have user consent. tor-browser#42605. - bootstrapOptions.testInternet = true; - } - this._addSimulateOptions(bootstrapOptions, regionCode);
// NOTE: The only `await` in this method is for `bootstrapAttempt.run`. @@ -1523,22 +1369,27 @@ export const TorConnect = { return; }
- if ( - result === "offline" && - (beginStage === TorConnectStage.Start || - beginStage === TorConnectStage.Offline) - ) { - this._tryAgain = true; - this._signalError(new TorConnectError(TorConnectError.Offline)); - - this._setStage(TorConnectStage.Offline); - return; - } - if (error) { lazy.logger.info("Bootstrap attempt error", error); - this._tryAgain = true; + + if ( + (beginStage === TorConnectStage.Start || + beginStage === TorConnectStage.Offline) && + (this.internetStatus === InternetStatus.Offline || this.simulateOffline) + ) { + // If we are currently offline, we assume the bootstrap error is caused + // by a general internet connection problem, so we show an "Offline" + // stage instead. + // NOTE: In principle, we may determine that we are offline prior to the + // bootstrap being thrown, but we do not want to cancel a bootstrap + // attempt prematurely in case the offline state is intermittent or + // incorrect. + this._signalError(new TorConnectError(TorConnectError.Offline)); + this._setStage(TorConnectStage.Offline); + return; + } + this._potentiallyBlocked = true; // Disable quickstart until we have a successful bootstrap. Services.prefs.setBoolPref(TorConnectPrefs.prompt_at_startup, true);
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/2d67220...
tbb-commits@lists.torproject.org