This is an automated email from the git hooks/post-receive script.
richard pushed a change to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
from abc0b7298eddb Bug 21952: Implement Onion-Location new d7b8ebd62f4c2 fixup! Add TorStrings module for localization new 3c85de34837cc fixup! Bug 40597: Implement TorSettings module new 207037ff0a0f2 squash! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection new 76f70fba50817 squash! Bug 27476: Implement about:torconnect captive portal within Tor Browser new 623e8a73c6976 fixup! Bug 40597: Implement TorSettings module
The 5 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: browser/components/preferences/preferences.js | 10 +- browser/components/preferences/preferences.xhtml | 5 +- browser/components/torconnect/TorConnectParent.jsm | 59 +- .../torconnect/content/aboutTorConnect.css | 189 ++- .../torconnect/content/aboutTorConnect.js | 570 +++++++-- .../torconnect/content/aboutTorConnect.xhtml | 43 +- .../components/torconnect/content/arrow-right.svg | 4 + browser/components/torconnect/content/bridge.svg | 5 + .../torconnect/content/connection-failure.svg | 5 + .../torconnect/content/connection-location.svg | 5 + browser/components/torconnect/content/globe.svg | 4 + .../torconnect/content/onion-slash-fillable.svg | 5 + browser/components/torconnect/jar.mn | 6 + .../torpreferences/content/bridgeQrDialog.jsm | 51 + .../torpreferences/content/bridgeQrDialog.xhtml | 23 + .../torpreferences/content/builtinBridgeDialog.jsm | 142 +++ .../content/builtinBridgeDialog.xhtml | 43 + ...gory.inc.xhtml => connectionCategory.inc.xhtml} | 8 +- .../torpreferences/content/connectionPane.js | 1315 ++++++++++++++++++++ .../torpreferences/content/connectionPane.xhtml | 177 +++ .../content/connectionSettingsDialog.jsm | 393 ++++++ .../content/connectionSettingsDialog.xhtml | 62 + .../components/torpreferences/content/network.svg | 0 .../torpreferences/content/provideBridgeDialog.jsm | 69 + .../content/provideBridgeDialog.xhtml | 21 + .../torpreferences/content/requestBridgeDialog.jsm | 32 +- .../content/requestBridgeDialog.xhtml | 10 +- .../torpreferences/content/torLogDialog.jsm | 18 + .../components/torpreferences/content/torPane.js | 940 -------------- .../torpreferences/content/torPane.xhtml | 157 --- .../torpreferences/content/torPreferences.css | 394 +++++- browser/components/torpreferences/jar.mn | 15 +- browser/modules/Moat.jsm | 122 +- browser/modules/TorConnect.jsm | 278 +++-- browser/modules/TorProtocolService.jsm | 100 +- browser/modules/TorSettings.jsm | 139 +-- browser/modules/TorStrings.jsm | 320 ++++- toolkit/modules/RemotePageAccessManager.jsm | 13 +- 38 files changed, 4109 insertions(+), 1643 deletions(-) create mode 100644 browser/components/torconnect/content/arrow-right.svg create mode 100644 browser/components/torconnect/content/bridge.svg create mode 100644 browser/components/torconnect/content/connection-failure.svg create mode 100644 browser/components/torconnect/content/connection-location.svg create mode 100644 browser/components/torconnect/content/globe.svg create mode 100644 browser/components/torconnect/content/onion-slash-fillable.svg create mode 100644 browser/components/torpreferences/content/bridgeQrDialog.jsm create mode 100644 browser/components/torpreferences/content/bridgeQrDialog.xhtml create mode 100644 browser/components/torpreferences/content/builtinBridgeDialog.jsm create mode 100644 browser/components/torpreferences/content/builtinBridgeDialog.xhtml rename browser/components/torpreferences/content/{torCategory.inc.xhtml => connectionCategory.inc.xhtml} (57%) create mode 100644 browser/components/torpreferences/content/connectionPane.js create mode 100644 browser/components/torpreferences/content/connectionPane.xhtml create mode 100644 browser/components/torpreferences/content/connectionSettingsDialog.jsm create mode 100644 browser/components/torpreferences/content/connectionSettingsDialog.xhtml copy toolkit/themes/shared/icons/defaultFavicon.svg => browser/components/torpreferences/content/network.svg (100%) create mode 100644 browser/components/torpreferences/content/provideBridgeDialog.jsm create mode 100644 browser/components/torpreferences/content/provideBridgeDialog.xhtml delete mode 100644 browser/components/torpreferences/content/torPane.js delete mode 100644 browser/components/torpreferences/content/torPane.xhtml
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
commit d7b8ebd62f4c2b4a12008a4b007c9049e8046c10 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Fri Feb 11 10:08:05 2022 +0100
fixup! Add TorStrings module for localization --- browser/modules/TorStrings.jsm | 320 ++++++++++++++++++++++++++++++++--------- 1 file changed, 256 insertions(+), 64 deletions(-)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm index 0ccbbb41a7824..eb1e7808b4b90 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -239,7 +239,7 @@ var TorStrings = { })() /* Security Level Strings */,
/* - Tor about:preferences#tor Strings + Tor about:preferences#connection Strings */ settings: (function() { let tsb = new TorDTDStringBundle( @@ -251,53 +251,172 @@ var TorStrings = { };
let retval = { - categoryTitle: getString("torPreferences.categoryTitle", "Tor"), - torPreferencesHeading: getString( - "torPreferences.torSettings", - "Tor Settings" - ), + categoryTitle: getString("torPreferences.categoryTitle", "Connection"), + // Message box torPreferencesDescription: getString( "torPreferences.torSettingsDescription", "Tor Browser routes your traffic over the Tor Network, run by thousands of volunteers around the world." ), - learnMore: getString("torPreferences.learnMore", "Learn More"), + // Status + statusInternetLabel: getString("torPreferences.statusInternetLabel", "Internet:"), + statusInternetTest: getString("torPreferences.statusInternetTest", "Test"), + statusInternetOnline: getString("torPreferences.statusInternetOnline", "Online"), + statusInternetOffline: getString("torPreferences.statusInternetOffline", "Offline"), + statusTorLabel: getString("torPreferences.statusTorLabel", "Tor Network:"), + statusTorConnected: getString("torPreferences.statusTorConnected", "Connected"), + statusTorNotConnected: getString("torPreferences.statusTorNotConnected", "Not Connected"), + statusTorBlocked: getString("torPreferences.statusTorBlocked", "Potentially Blocked"), + learnMore: getString("torPreferences.learnMore", "Learn more"), + // Quickstart quickstartHeading: getString("torPreferences.quickstart", "Quickstart"), - quickstartDescription: getString("torPreferences.quickstartDescription", "Quickstart allows Tor Browser to connect automatically."), + quickstartDescription: getString( + "torPreferences.quickstartDescriptionLong", + "Quickstart connects Tor Browser to the Tor Network automatically when launched, based on your last used connection settings." + ), quickstartCheckbox : getString("torPreferences.quickstartCheckbox", "Always connect automatically"), + // Bridge settings bridgesHeading: getString("torPreferences.bridges", "Bridges"), bridgesDescription: getString( "torPreferences.bridgesDescription", "Bridges help you access the Tor Network in places where Tor is blocked. Depending on where you are, one bridge may work better than another." ), - useBridge: getString("torPreferences.useBridge", "Use a bridge"), - selectBridge: getString( - "torsettings.useBridges.default", - "Select a bridge" - ), - requestBridgeFromTorProject: getString( + bridgeLocation: getString("torPreferences.bridgeLocation", "Your location"), + bridgeLocationAutomatic: getString("torPreferences.bridgeLocationAutomatic", "Automatic"), + bridgeChooseForMe: getString("torPreferences.bridgeChooseForMe", "Choose a Bridge For Me\u2026"), + bridgeCurrent: getString("torPreferences.bridgeBadgeCurrent", "Your Current Bridges"), + bridgeId: getString("torPreferences.bridgeId", "#1 bridge: #2"), + remove: getString("torPreferences.remove", "Remove"), + bridgeDisableBuiltIn: getString("torPreferences.bridgeDisableBuiltIn", "Disable built-in bridges"), + bridgeShare: getString( + "torPreferences.bridgeShare", + "Share your bridge by presenting the QR code or copying its bridge line." + ), + bridgeCopy: getString("torPreferences.bridgeCopy", "Copy Bridge Address"), + copied: getString("torPreferences.copied", "Copied!"), + bridgeShowAll: getString("torPreferences.bridgeShowAll", "Show all bridges"), + bridgeRemoveAll: getString("torPreferences.bridgeRemoveAll", "Remove all bridges"), + bridgeAdd: getString("torPreferences.bridgeAdd", "Add a New Bridge"), + bridgeSelectBrowserBuiltin: getString( + "torPreferences.bridgeSelectBrowserBuiltin", + "Choose from one of Tor Browser’s built-in bridges" + ), + bridgeSelectBuiltin: getString( + "torPreferences.bridgeSelectBuiltin", + "Select a Built-In Bridge\u2026" + ), + bridgeRequestFromTorProject: getString( "torsettings.useBridges.bridgeDB", "Request a bridge from torproject.org" ), - requestNewBridge: getString( - "torPreferences.requestNewBridge", - "Request a New Bridge\u2026" + bridgeRequest: getString( + "torPreferences.bridgeRequest", + "Request a Bridge\u2026" + ), + bridgeEnterKnown: getString( + "torPreferences.bridgeEnterKnown", + "Enter a bridge address you already know" + ), + bridgeAddManually: getString( + "torPreferences.bridgeAddManually", + "Add a Bridge Manually\u2026" + ), + // Advanced settings + advancedHeading: getString("torPreferences.advanced", "Advanced"), + advancedLabel: getString( + "torPreferences.advancedDescription", + "Configure how Tor Browser connects to the internet" ), - provideBridge: getString( - "torPreferences.provideBridge", - "Provide a bridge" + advancedButton: getString("torPreferences.advancedButton", "Settings\u2026"), + showTorDaemonLogs: getString( + "torPreferences.viewTorLogs", + "View the Tor logs" ), - provideBridgeDirections: getString( - "torsettings.useBridges.label", - "Enter bridge information from a trusted source." + showLogs: getString("torPreferences.viewLogs", "View Logs\u2026"), + // Scan bridge QR dialog + scanQrTitle: getString("torPreferences.scanQrTitle", "Scan the QR code"), + // Builtin bridges dialog + builtinBridgeTitle: getString( + "torPreferences.builtinBridgeTitle", + "Built-In Bridges" + ), + builtinBridgeHeader: getString( + "torPreferences.builtinBridgeHeader", + "Select a Built-In Bridge" + ), + builtinBridgeDescription: getString( + "torPreferences.builtinBridgeDescription", + "Tor Browser includes some specific types of bridges known as “pluggable transports”." + ), + builtinBridgeObfs4: getString( + "torPreferences.builtinBridgeObfs4", + "obfs4" + ), + builtinBridgeObfs4Description: getString( + "torPreferences.builtinBridgeObfs4Description", + "obfs4 is a type of built-in bridge that makes your Tor traffic look random. They are also less likely to be blocked than their predecessors, obfs3 bridges." + ), + builtinBridgeSnowflake: getString( + "torPreferences.builtinBridgeSnowflake", + "Snowflake" + ), + builtinBridgeSnowflakeDescription: getString( + "torPreferences.builtinBridgeSnowflakeDescription", + "Snowflake is a built-in bridge that defeats censorship by routing your connection through Snowflake proxies, ran by volunteers." + ), + builtinBridgeMeekAzure: getString( + "torPreferences.builtinBridgeMeekAzure", + "meek-azure" + ), + builtinBridgeMeekAzureDescription: getString( + "torPreferences.builtinBridgeMeekAzureDescription", + "meek-azure is a built-in bridge that makes it look like you are using a Microsoft web site instead of using Tor." + ), + // Request bridges dialog + requestBridgeDialogTitle: getString( + "torPreferences.requestBridgeDialogTitle", + "Request Bridge" + ), + submitCaptcha: getString( + "torsettings.useBridges.captchaSubmit", + "Submit" + ), + contactingBridgeDB: getString( + "torPreferences.requestBridgeDialogWaitPrompt", + "Contacting BridgeDB. Please Wait." + ), + solveTheCaptcha: getString( + "torPreferences.requestBridgeDialogSolvePrompt", + "Solve the CAPTCHA to request a bridge." + ), + captchaTextboxPlaceholder: getString( + "torsettings.useBridges.captchaSolution.placeholder", + "Enter the characters from the image" + ), + incorrectCaptcha: getString( + "torPreferences.requestBridgeErrorBadSolution", + "The solution is not correct. Please try again." + ), + // Provide bridge dialog + provideBridgeTitle: getString( + "torPreferences.provideBridgeTitle", + "Provide Bridge" + ), + provideBridgeHeader: getString( + "torPreferences.provideBridgeHeader", + "Enter bridge information from a trusted source" ), provideBridgePlaceholder: getString( - "torsettings.useBridges.placeholder", + "torsettings.bridgePlaceholder", "type address:port (one per line)" ), - advancedHeading: getString("torPreferences.advanced", "Advanced"), - advancedDescription: getString( - "torPreferences.advancedDescription", - "Configure how Tor Browser connects to the internet." + // Connection settings dialog + connectionSettingsDialogTitle: getString( + "torPreferences.connectionSettingsDialogTitle", + "Connection Settings" + ), + connectionSettingsDialogHeader: getString( + "torPreferences.connectionSettingsDialogHeader", + "Configure how Tor Browser connects to the Internet" ), useLocalProxy: getString("torsettings.useProxy.checkbox", "I use a proxy to connect to the Internet"), proxyType: getString("torsettings.useProxy.type", "Proxy Type"), @@ -328,35 +447,7 @@ var TorStrings = { "torPreferences.firewallPortsPlaceholder", "Comma-seperated values" ), - requestBridgeDialogTitle: getString( - "torPreferences.requestBridgeDialogTitle", - "Request Bridge" - ), - submitCaptcha: getString( - "torsettings.useBridges.captchaSubmit", - "Submit" - ), - contactingBridgeDB: getString( - "torPreferences.requestBridgeDialogWaitPrompt", - "Contacting BridgeDB. Please Wait." - ), - solveTheCaptcha: getString( - "torPreferences.requestBridgeDialogSolvePrompt", - "Solve the CAPTCHA to request a bridge." - ), - captchaTextboxPlaceholder: getString( - "torsettings.useBridges.captchaSolution.placeholder", - "Enter the characters from the image" - ), - incorrectCaptcha: getString( - "torPreferences.requestBridgeErrorBadSolution", - "The solution is not correct. Please try again." - ), - showTorDaemonLogs: getString( - "torPreferences.viewTorLogs", - "View the Tor logs." - ), - showLogs: getString("torPreferences.viewLogs", "View Logs\u2026"), + // Log dialog torLogDialogTitle: getString( "torPreferences.torLogsDialogTitle", "Tor Logs" @@ -365,7 +456,6 @@ var TorStrings = {
learnMoreTorBrowserURL: `https://tb-manual.torproject.org/$%7BgetLocale()%7D/about/%60, learnMoreBridgesURL: `https://tb-manual.torproject.org/$%7BgetLocale()%7D/bridges/%60, - learnMoreNetworkSettingsURL: `about:blank`, };
return retval; @@ -415,14 +505,89 @@ var TorStrings = { "Tor failed to establish a Tor network connection." ),
+ couldNotConnect: getStringNet( + "torConnect.couldNotConnect", + "Tor Browser could not connect to Tor" + ), + + configureConnection: getStringNet( + "torConnect.assistDescriptionConfigure", + "configure your connection" + ), + + assistDescription: getStringNet( + "torConnect.assistDescription", + "If Tor is blocked in your location, trying a bridge may help. Connection assist can choose one for you using your location, or you can #1 manually instead." + ), + + tryingBridge: getStringNet( + "torConnect.tryingBridge", + "Trying a bridge…" + ), + + tryingBridgeAgain: getStringNet( + "torConnect.tryingBridge", + "Trying one more time…" + ), + + addLocation: getStringNet( + "torConnect.addLocation", + "Add your location settings" + ), + + addLocationDescription: getStringNet( + "torConnect.addLocationDescription", + "Tor Browser needs to know your location in order to choose the right bridge for you. If you’d rather not share your location, #1 manually instead." + ), + + errorLocation: getStringNet( + "torConnect.errorLocation", + "Tor Browser couldn’t locate you" + ), + + errorLocationDescription: getStringNet( + "torConnect.errorLocationDescription", + "Tor Browser still couldn’t connect to Tor. Please check your location settings are correct and try again." + ), + + finalError: getStringNet( + "torConnect.finalError", + "Tor Browser still cannot connect", + ), + + finalErrorDescription: getStringNet( + "torConnect.finalErrorDescription", + "Despite its best efforts, connection assist was not able to connect to Tor. Try troubleshooting your connection and adding a bridge manually instead.", + ), + + breadcrumbAssist: getStringNet( + "torConnect.breadcrumbAssist", + "Connection assist" + ), + + breadcrumbLocation: getStringNet( + "torConnect.breadcrumbLocation", + "Location settings" + ), + + breadcrumbTryBridge: getStringNet( + "torConnect.breadcrumbTryBridge", + "Try a bridge" + ), + + restartTorBrowser: getStringNet( + "torConnect.restartTorBrowser", + "Restart Tor Browser" + ), + torConfigure: getStringNet( - "torsettings.wizard.title.configure", - "Tor Network Settings" + "torConnect.configureConnection", + "Configure Connection…" ),
- copyLog: getStringNet( - "torConnect.copyLog", - "Copy Tor Logs" + viewLog: getStringNet( + "torConnect.viewLog", + "View logs…" ),
torConnectButton: getStringNet("torSettings.connect", "Connect"), @@ -442,9 +607,36 @@ var TorStrings = { tryAgain: getStringNet("torConnect.tryAgain", "Try connecting again"), offline: getStringNet("torConnect.offline", "Offline"),
- // tor connect strings for message box in about:preferences#tor + // tor connect strings for message box in about:preferences#connection connectMessage: getStringNet("torConnect.connectMessage", "Changes to Tor Settings will not take effect until you connect"), tryAgainMessage: getStringNet("torConnect.tryAgainMessage", "Tor Browser has failed to establish a connection to the Tor Network"), + + yourLocation: getStringNet("torConnect.yourLocation", "Your Location"), + + tryBridge: getStringNet("torConnect.tryBridge", "Try a Bridge"), + + selectCountryRegion: getStringNet( + "torConnect.selectCountryRegion", + "Select Country or Region", + ), + + // TorConnect.jsm error messages + autoBootstrappingFailed: getStringNet( + "torConnect.autoBootstrappingFailed", + "Automatic configuration failed" + ), + autoBootstrappingAllFailed: getStringNet( + "torConnect.autoBootstrappingFailed", + "None of the configurations we tried worked" + ), + cannotDetermineCountry: getStringNet( + "torConnect.cannotDetermineCountry", + "Unable to determine user country" + ), + noSettingsForCountry: getStringNet( + "torConnect.noSettingsForCountry", + "No settings available for your location" + ), }; })(),
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
commit 3c85de34837cc10c2d182465e40ef6c02cc5d18f Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Feb 10 10:03:39 2022 +0100
fixup! Bug 40597: Implement TorSettings module --- browser/modules/Moat.jsm | 122 ++++++++++----- browser/modules/TorConnect.jsm | 278 +++++++++++++++++++++++---------- browser/modules/TorProtocolService.jsm | 100 +++++++----- browser/modules/TorSettings.jsm | 138 +++++++--------- 4 files changed, 399 insertions(+), 239 deletions(-)
diff --git a/browser/modules/Moat.jsm b/browser/modules/Moat.jsm index d02075e4412f2..2995a9148f0a3 100644 --- a/browser/modules/Moat.jsm +++ b/browser/modules/Moat.jsm @@ -16,7 +16,7 @@ const { TorProtocolService } = ChromeUtils.import( "resource:///modules/TorProtocolService.jsm" );
-const { TorSettings, TorBridgeSource, TorProxyType } = ChromeUtils.import( +const { TorSettings, TorBridgeSource } = ChromeUtils.import( "resource:///modules/TorSettings.jsm" );
@@ -384,6 +384,42 @@ class MoatResponseListener { } }
+class InternetTestResponseListener { + constructor() { + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + + // callers wait on this for final response + status() { + return this._promise; + } + + onStartRequest(request) {} + + // resolve or reject our Promise + onStopRequest(request, status) { + let statuses = {}; + try { + statuses = { + components: status, + successful: Components.isSuccessCode(status), + http: request.responseStatus, + }; + } catch (err) { + this._reject(err); + } + this._resolve(statuses); + } + + onDataAvailable(request, stream, offset, length) { + // We do not care of the actual data, as long as we have a successful + // connection + } +} + // constructs the json objects and sends the request over moat class MoatRPC { constructor() { @@ -391,6 +427,10 @@ class MoatRPC { this._inited = false; }
+ get inited() { + return this._inited; + } + async init() { if (this._inited) { throw new Error("MoatRPC: Already initialized"); @@ -408,7 +448,7 @@ class MoatRPC { this._inited = false; }
- async _makeRequest(procedure, args) { + _makeHttpHandler(uriString) { if (!this._inited) { throw new Error("MoatRPC: Not initialized"); } @@ -437,16 +477,12 @@ class MoatRPC { undefined );
- const procedureURIString = `${Services.prefs.getStringPref( - TorLauncherPrefs.moat_service - )}/${procedure}`; - - const procedureURI = Services.io.newURI(procedureURIString); + const uri = Services.io.newURI(uriString); // There does not seem to be a way to directly create an nsILoadInfo from // JavaScript, so we create a throw away non-proxied channel to get one. const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; const loadInfo = Services.io.newChannelFromURI( - procedureURI, + uri, undefined, Services.scriptSecurityManager.getSystemPrincipal(), undefined, @@ -458,7 +494,7 @@ class MoatRPC { .getProtocolHandler("http") .QueryInterface(Ci.nsIHttpProtocolHandler); const ch = httpHandler - .newProxiedChannel(procedureURI, proxyInfo, 0, undefined, loadInfo) + .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo) .QueryInterface(Ci.nsIHttpChannel);
// remove all headers except for 'Host" @@ -472,6 +508,15 @@ class MoatRPC { }); headers.forEach(key => ch.setRequestHeader(key, "", false));
+ return ch; + } + + async _makeRequest(procedure, args) { + const procedureURIString = `${Services.prefs.getStringPref( + TorLauncherPrefs.moat_service + )}/${procedure}`; + const ch = this._makeHttpHandler(procedureURIString); + // Arrange for the POST data to be sent. const argsJson = JSON.stringify(args);
@@ -495,6 +540,18 @@ class MoatRPC { return JSON.parse(responseJSON); }
+ async testInternetConnection() { + const uri = `${Services.prefs.getStringPref( + TorLauncherPrefs.moat_service + )}/circumvention/countries`; + const ch = this._makeHttpHandler(uri); + ch.requestMethod = "HEAD"; + + const listener = new InternetTestResponseListener(); + await ch.asyncOpen(listener, ch); + return listener.status(); + } + // // Moat APIs // @@ -508,7 +565,6 @@ class MoatRPC { // - image: a base64 encoded jpeg with the captcha to complete // - challenge: a nonce/cookie string associated with this request async fetch(transports) { - if ( // ensure this is an array Array.isArray(transports) && @@ -588,10 +644,10 @@ class MoatRPC { // In the event of error, just return null _fixupSettings(settings) { try { - let retval = TorSettings.defaultSettings() + let retval = TorSettings.defaultSettings(); if ("bridges" in settings) { retval.bridges.enabled = true; - switch(settings.bridges.source) { + switch (settings.bridges.source) { case "builtin": retval.bridges.source = TorBridgeSource.BuiltIn; retval.bridges.builtin_type = settings.bridges.type; @@ -606,12 +662,17 @@ class MoatRPC { retval.bridges.source = TorBridgeSource.BridgeDB; if (settings.bridges.bridge_strings) { retval.bridges.bridge_strings = settings.bridges.bridge_strings; + retval.bridges.disabled_strings = []; } else { - throw new Error("MoatRPC::_fixupSettings(): Received no bridge-strings for BridgeDB bridge source"); + throw new Error( + "MoatRPC::_fixupSettings(): Received no bridge-strings for BridgeDB bridge source" + ); } break; default: - throw new Error(`MoatRPC::_fixupSettings(): Unexpected bridge source '${settings.bridges.source}'`); + throw new Error( + `MoatRPC::_fixupSettings(): Unexpected bridge source '${settings.bridges.source}'` + ); } } if ("proxy" in settings) { @@ -621,7 +682,7 @@ class MoatRPC { // TODO: populate firewall settings } return retval; - } catch(ex) { + } catch (ex) { console.log(ex.message); return null; } @@ -661,14 +722,16 @@ class MoatRPC { async circumvention_settings(transports, country) { const args = { transports: transports ? transports : [], - country: country, + country, }; const response = await this._makeRequest("circumvention/settings", args); if ("errors" in response) { const code = response.errors[0].code; const detail = response.errors[0].detail; if (code == 406) { - console.log("MoatRPC::circumvention_settings(): Cannot automatically determine user's country-code"); + console.log( + "MoatRPC::circumvention_settings(): Cannot automatically determine user's country-code" + ); // cannot determine user's country return null; } @@ -681,26 +744,13 @@ class MoatRPC { return []; }
- // Request a copy of the censorship circumvention map (as if cirumvention_settings were - // queried for all country codes) + // Request a list of country codes with available censorship circumvention settings // - // returns a map whose key is an ISO 3166-1 alpha-2 country code and whose - // values are arrays of settings objects - async circumvention_map() { - const args = { }; - const response = await this._makeRequest("circumvention/map", args); - if ("errors" in response) { - const code = response.errors[0].code; - const detail = response.errors[0].detail; - throw new Error(`MoatRPC: ${detail} (${code})`); - } - - let map = new Map(); - for (const [country, config] of Object.entries(response)) { - map.set(country, this._fixupSettingsList(config.settings)); - } - - return map; + // returns an array of ISO 3166-1 alpha-2 country codes which we can query settings + // for + async circumvention_countries() { + const args = {}; + return this._makeRequest("circumvention/countries", args); }
// Request a copy of the builtin bridges, takes the following parameters: diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm index c7ab480e2be06..13c1f54d2ee97 100644 --- a/browser/modules/TorConnect.jsm +++ b/browser/modules/TorConnect.jsm @@ -1,11 +1,15 @@ "use strict";
-var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState"]; +var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState", "TorCensorshipLevel"];
const { Services } = ChromeUtils.import( "resource://gre/modules/Services.jsm" );
+const { setTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + const { BrowserWindowTracker } = ChromeUtils.import( "resource:///modules/BrowserWindowTracker.jsm" ); @@ -22,6 +26,8 @@ const { TorSettings, TorSettingsTopics, TorBridgeSource, TorBuiltinBridgeTypes, "resource:///modules/TorSettings.jsm" );
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm");
/* Browser observer topis */ @@ -31,7 +37,11 @@ const BrowserTopics = Object.freeze({
/* Relevant prefs used by tor-launcher */ const TorLauncherPrefs = Object.freeze({ - prompt_at_startup: "extensions.torlauncher.prompt_at_startup", + prompt_at_startup: "extensions.torlauncher.prompt_at_startup", +}); + +const TorConnectPrefs = Object.freeze({ + censorship_level: "torbrowser.debug.censorship_level", });
const TorConnectState = Object.freeze({ @@ -51,6 +61,17 @@ const TorConnectState = Object.freeze({ Disabled: "Disabled", });
+const TorCensorshipLevel = Object.freeze({ + /* No censorship detected */ + None: 0, + /* Moderate censorship detected, autobootstrap may evade it */ + Moderate: 1, + /* Severe censorship detected, but connection may still succeed */ + Severe: 2, + /* Extreme censorship detected, connection will always end in an error */ + Extreme: 3, +}); + /* TorConnect State Transitions
@@ -197,12 +218,31 @@ class StateCallback { } }
+// async method to sleep for a given amount of time +const debug_sleep = async (ms) => { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); + }); +} + const TorConnect = (() => { let retval = {
_state: TorConnectState.Initial, _bootstrapProgress: 0, _bootstrapStatus: null, + _detectedCensorshiplevel: TorCensorshipLevel.None, + // list of country codes Moat has settings for + _countryCodes: [], + _countryNames: Object.freeze((() => { + const codes = Services.intl.getAvailableLocaleDisplayNames("region"); + const names = Services.intl.getRegionDisplayNames(undefined, codes); + let codesNames = {}; + for (let i = 0; i < codes.length; i++) { + codesNames[codes[i]] = names[i]; + } + return codesNames; + })()), _errorMessage: null, _errorDetails: null, _logHasWarningOrError: false, @@ -254,6 +294,22 @@ const TorConnect = (() => { [TorConnectState.Bootstrapping, new StateCallback(TorConnectState.Bootstrapping, async function() { // wait until bootstrap completes or we get an error await new Promise(async (resolve, reject) => { + + // we reset the bootstrap failure count so users can manually try settings without the + // censorship circumvention state machine getting in the way + TorConnect._detectedCensorshipLevel = TorCensorshipLevel.None; + + // debug hook to simulate censorship preventing bootstrapping + if (Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) > 0) { + this.on_transition = (nextState) => { + resolve(); + }; + await debug_sleep(1500); + TorConnect._changeState(TorConnectState.Error, "Bootstrap failed (for debugging purposes)", "Error: Censorship simulation", true); + TorProtocolService._torBootstrapDebugSetError(); + return; + } + const tbr = new TorBootstrapRequest(); this.on_transition = async (nextState) => { if (nextState === TorConnectState.Configuring) { @@ -270,7 +326,7 @@ const TorConnect = (() => { TorConnect._changeState(TorConnectState.Bootstrapped); }; tbr.onbootstraperror = (message, details) => { - TorConnect._changeState(TorConnectState.Error, message, details); + TorConnect._changeState(TorConnectState.Error, message, details, true); };
tbr.bootstrap(); @@ -283,91 +339,123 @@ const TorConnect = (() => { resolve(); };
+ // debug hook to simulate censorship preventing bootstrapping + { + const censorshipLevel = Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0); + if (censorshipLevel > 1) { + this.on_transition = (nextState) => { + resolve(); + }; + // always fail even after manually selecting location specific settings + if (censorshipLevel == 3) { + await debug_sleep(2500); + TorConnect._changeState(TorConnectState.Error, "Error: Extreme Censorship simulation", "", true); + return; + // only fail after auto selecting, manually selecting succeeds + } else if (censorshipLevel == 2 && !countryCode) { + await debug_sleep(2500); + TorConnect._changeState(TorConnectState.Error, "Error: Severe Censorship simulation", "", true); + return; + } + TorProtocolService._torBootstrapDebugSetError(); + } + } + + const throw_error = (message, details) => { + let err = new Error(message); + err.details = details; + throw err; + }; + // lookup user's potential censorship circumvention settings from Moat service try { this.mrpc = new MoatRPC(); await this.mrpc.init();
+ if (this.transitioning) return; + this.settings = await this.mrpc.circumvention_settings([...TorBuiltinBridgeTypes, "vanilla"], countryCode);
if (this.transitioning) return;
if (this.settings === null) { // unable to determine country - TorConnect._changeState(TorConnectState.Error, "Unable to determine user country", "DETAILS_STRING"); - return; + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.cannotDetermineCountry); } else if (this.settings.length === 0) { // no settings available for country - TorConnect._changeState(TorConnectState.Error, "No settings available for your location", "DETAILS_STRING"); - return; + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.noSettingsForCountry); } - } catch (err) { - TorConnect._changeState(TorConnectState.Error, err?.message, err?.details); - return; - } finally { - // important to uninit MoatRPC object or else the pt process will live as long as tor-browser - this.mrpc?.uninit(); - }
- // apply each of our settings and try to bootstrap with each - try { - this.originalSettings = TorSettings.getSettings(); - - let index = 0; - for (let currentSetting of this.settings) { - // let us early out if user cancels - if (this.transitioning) return; - - console.log(`TorConnect: Attempting Bootstrap with configuration ${++index}/${this.settings.length}`); + // apply each of our settings and try to bootstrap with each + try { + this.originalSettings = TorSettings.getSettings(); + + for (const [index, currentSetting] of this.settings.entries()) { + + // we want to break here so we can fall through and restore original settings + if (this.transitioning) break; + + console.log(`TorConnect: Attempting Bootstrap with configuration ${index+1}/${this.settings.length}`); + + TorSettings.setSettings(currentSetting); + await TorSettings.applySettings(); + + // build out our bootstrap request + const tbr = new TorBootstrapRequest(); + tbr.onbootstrapstatus = (progress, status) => { + TorConnect._updateBootstrapStatus(progress, status); + }; + tbr.onbootstraperror = (message, details) => { + console.log(`TorConnect: Auto-Bootstrap error => ${message}; ${details}`); + }; + + // update transition callback for user cancel + this.on_transition = async (nextState) => { + if (nextState === TorConnectState.Configuring) { + await tbr.cancel(); + } + resolve(); + }; + + // begin bootstrap + if (await tbr.bootstrap()) { + // persist the current settings to preferences + TorSettings.saveToPrefs(); + TorConnect._changeState(TorConnectState.Bootstrapped); + return; + } + }
- TorSettings.setSettings(currentSetting); + // bootstrapped failed for all potential settings, so reset daemon to use original + TorSettings.setSettings(this.originalSettings); await TorSettings.applySettings(); + TorSettings.saveToPrefs();
- // build out our bootstrap request - const tbr = new TorBootstrapRequest(); - tbr.onbootstrapstatus = (progress, status) => { - TorConnect._updateBootstrapStatus(progress, status); - }; - tbr.onbootstraperror = (message, details) => { - console.log(`TorConnect: Auto-Bootstrap error => ${message}; ${details}`); - }; - - // update transition callback for user cancel - this.on_transition = async (nextState) => { - if (nextState === TorConnectState.Configuring) { - await tbr.cancel(); - } - resolve(); - }; - - // begin bootstrap - if (await tbr.bootstrap()) { - // persist the current settings to preferences - TorSettings.saveToPrefs(); - TorConnect._changeState(TorConnectState.Bootstrapped); - return; + // only explicitly change state here if something else has not transitioned us + if (!this.transitioning) { + throw_error(TorStrings.torConnect.autoBootstrappingFailed, TorStrings.torConnect.autoBootstrappingAllFailed); } + return; + } catch (err) { + // restore original settings in case of error + try { + TorSettings.setSettings(this.originalSettings); + await TorSettings.applySettings(); + } catch(err) { + console.log(`TorConnect: Failed to restore original settings => ${err}`); + } + // throw to outer catch to transition us + throw err; } - // bootstrapped failed for all potential settings, so reset daemon to use original - TorSettings.setSettings(this.originalSettings); - await TorSettings.applySettings(); - TorSettings.saveToPrefs(); - - // only explicitly change state here if something else has not transitioned us - if (!this.transitioning) { - TorConnect._changeState(TorConnectState.Error, "AutoBootstrapping failed", "DETAILS_STRING"); - } - return; - } catch (err) { - // restore original settings in case of error - try { - TorSettings.setSettings(this.originalSettings); - await TorSettings.applySettings(); - } catch(err) { - console.log(`TorConnect: Failed to restore original settings => ${err}`); + } catch(err) { + if (this.mrpc?.inited) { + // lookup countries which have settings available + TorConnect._countryCodes = await this.mrpc.circumvention_countries(); } - TorConnect._changeState(TorConnectState.Error, err?.message, err?.details); - return; + TorConnect._changeState(TorConnectState.Error, err?.message, err?.details, true); + } finally { + // important to uninit MoatRPC object or else the pt process will live as long as tor-browser + this.mrpc?.uninit(); } }); })], @@ -380,7 +468,7 @@ const TorConnect = (() => { }); })], /* Error */ - [TorConnectState.Error, new StateCallback(TorConnectState.Error, async function(errorMessage, errorDetails) { + [TorConnectState.Error, new StateCallback(TorConnectState.Error, async function(errorMessage, errorDetails, bootstrappingFailure) { await new Promise((resolve, reject) => { this.on_transition = async(nextState) => { resolve(); @@ -389,7 +477,11 @@ const TorConnect = (() => { TorConnect._errorMessage = errorMessage; TorConnect._errorDetails = errorDetails;
- Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError); + if (bootstrappingFailure && TorConnect._detectedCensorshipLevel < TorCensorshipLevel.Extreme) { + TorConnect._detectedCensorshipLevel += 1; + } + + Services.obs.notifyObservers({message: errorMessage, details: errorDetails, censorshipLevel: TorConnect.detectedCensorshipLevel}, TorConnectTopics.BootstrapError);
TorConnect._changeState(TorConnectState.Configuring); }); @@ -523,6 +615,10 @@ const TorConnect = (() => { return this._bootstrapStatus; },
+ get detectedCensorshipLevel() { + return this._detectedCensorshipLevel; + }, + get errorMessage() { return this._errorMessage; }, @@ -535,6 +631,14 @@ const TorConnect = (() => { return this._logHasWarningOrError; },
+ get countryCodes() { + return this._countryCodes; + }, + + get countryNames() { + return this._countryNames; + }, + /* These functions allow external consumers to tell TorConnect to transition states */ @@ -564,7 +668,7 @@ const TorConnect = (() => { */ openTorPreferences: function() { const win = BrowserWindowTracker.getTopWindow(); - win.switchToTabHavingURI("about:preferences#tor", true); + win.switchToTabHavingURI("about:preferences#connection", true); },
openTorConnect: function() { @@ -572,19 +676,27 @@ const TorConnect = (() => { win.switchToTabHavingURI("about:torconnect", true, {ignoreQueryString: true}); },
- copyTorLogs: function() { - // Copy tor log messages to the system clipboard. - const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( - Ci.nsIClipboardHelper - ); - const countObj = { value: 0 }; - chSvc.copyString(TorProtocolService.getLog(countObj)); - const count = countObj.value; - return TorLauncherUtil.getFormattedLocalizedString( - "copiedNLogMessagesShort", - [count], - 1 - ); + viewTorLogs: function() { + const win = BrowserWindowTracker.getTopWindow(); + win.switchToTabHavingURI("about:preferences#connection-viewlogs", true); + }, + + getCountryCodes: async function() { + // Difference with the getter: this is to be called by TorConnectParent, and downloads + // the country codes if they are not already in cache. + if (this._countryCodes.length) { + return this._countryCodes; + } + const mrpc = new MoatRPC(); + try { + await mrpc.init(); + this._countryCodes = await mrpc.circumvention_countries(); + } catch(err) { + console.log("An error occurred while fetching country codes", err); + } finally { + mrpc.uninit(); + } + return this._countryCodes; },
getRedirectURL: function(url) { diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm index ac6d643691f6b..aa27e13e11711 100644 --- a/browser/modules/TorProtocolService.jsm +++ b/browser/modules/TorProtocolService.jsm @@ -2,16 +2,21 @@
"use strict";
-var EXPORTED_SYMBOLS = ["TorProtocolService", "TorProcessStatus", "TorTopics", "TorBootstrapRequest"]; +var EXPORTED_SYMBOLS = [ + "TorProtocolService", + "TorProcessStatus", + "TorTopics", + "TorBootstrapRequest", +];
-const { Services } = ChromeUtils.import( - "resource://gre/modules/Services.jsm" -); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { setTimeout, clearTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +);
const { TorLauncherUtil } = ChromeUtils.import( - "resource://torlauncher/modules/tl-util.jsm" + "resource://torlauncher/modules/tl-util.jsm" );
// see tl-process.js @@ -24,24 +29,18 @@ const TorProcessStatus = Object.freeze({
/* tor-launcher observer topics */ const TorTopics = Object.freeze({ - BootstrapStatus: "TorBootstrapStatus", - BootstrapError: "TorBootstrapError", - ProcessExited: "TorProcessExited", - LogHasWarnOrErr: "TorLogHasWarnOrErr", + BootstrapStatus: "TorBootstrapStatus", + BootstrapError: "TorBootstrapError", + ProcessExited: "TorProcessExited", + LogHasWarnOrErr: "TorLogHasWarnOrErr", });
/* Browser observer topis */ const BrowserTopics = Object.freeze({ - ProfileAfterChange: "profile-after-change", + ProfileAfterChange: "profile-after-change", });
var TorProtocolService = { - _TorLauncherUtil: function() { - let { TorLauncherUtil } = ChromeUtils.import( - "resource://torlauncher/modules/tl-util.jsm" - ); - return TorLauncherUtil; - }(), _TorLauncherProtocolService: null, _TorProcessService: null,
@@ -58,13 +57,12 @@ var TorProtocolService = { if (topic === BrowserTopics.ProfileAfterChange) { // we have to delay init'ing this or else the crypto service inits too early without a profile // which breaks the password manager - this._TorLauncherProtocolService = Cc["@torproject.org/torlauncher-protocol-service;1"].getService( - Ci.nsISupports - ).wrappedJSObject; - this._TorProcessService = Cc["@torproject.org/torlauncher-process-service;1"].getService( - Ci.nsISupports - ).wrappedJSObject, - + this._TorLauncherProtocolService = Cc[ + "@torproject.org/torlauncher-protocol-service;1" + ].getService(Ci.nsISupports).wrappedJSObject; + this._TorProcessService = Cc[ + "@torproject.org/torlauncher-process-service;1" + ].getService(Ci.nsISupports).wrappedJSObject; Services.obs.removeObserver(this, topic); } }, @@ -171,7 +169,12 @@ var TorProtocolService = { }
let errorObject = {}; - if (! await this._TorLauncherProtocolService.TorSetConfWithReply(settingsObject, errorObject)) { + if ( + !(await this._TorLauncherProtocolService.TorSetConfWithReply( + settingsObject, + errorObject + )) + ) { throw new Error(errorObject.details); }
@@ -195,9 +198,7 @@ var TorProtocolService = { let lineArray = await this._readSetting(aSetting); if (lineArray.length != 1) { throw new Error( - `Expected an array with length 1 but received array of length ${ - lineArray.length - }` + `Expected an array with length 1 but received array of length ${lineArray.length}` ); }
@@ -216,9 +217,7 @@ var TorProtocolService = { let lineArray = await this._readSetting(aSetting); if (lineArray.length != 1) { throw new Error( - `Expected an array with length 1 but received array of length ${ - lineArray.length - }` + `Expected an array with length 1 but received array of length ${lineArray.length}` ); } return lineArray[0]; @@ -260,7 +259,7 @@ var TorProtocolService = {
// true if we launched and control tor, false if using system tor get ownsTorDaemon() { - return this._TorLauncherUtil.shouldStartAndOwnTor; + return TorLauncherUtil.shouldStartAndOwnTor; },
// Assumes `ownsTorDaemon` is true @@ -269,7 +268,9 @@ var TorProtocolService = { "DisableNetwork", true ); - if (TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply)) { + if ( + TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply) + ) { return reply.retVal; } return true; @@ -279,13 +280,18 @@ var TorProtocolService = { let settings = {}; settings.DisableNetwork = false; let errorObject = {}; - if (! await this._TorLauncherProtocolService.TorSetConfWithReply(settings, errorObject)) { + if ( + !(await this._TorLauncherProtocolService.TorSetConfWithReply( + settings, + errorObject + )) + ) { throw new Error(errorObject.details); } },
async sendCommand(cmd) { - return await this._TorLauncherProtocolService.TorSendCommand(cmd); + return this._TorLauncherProtocolService.TorSendCommand(cmd); },
retrieveBootstrapStatus() { @@ -294,7 +300,7 @@ var TorProtocolService = {
_GetSaveSettingsErrorMessage(aDetails) { try { - return this._TorLauncherUtil.getSaveSettingsErrorMessage(aDetails); + return TorLauncherUtil.getSaveSettingsErrorMessage(aDetails); } catch (e) { console.log("GetSaveSettingsErrorMessage error", e); return "Unexpected Error"; @@ -305,7 +311,10 @@ var TorProtocolService = { let result = false; const error = {}; try { - result = await this._TorLauncherProtocolService.TorSetConfWithReply(settings, error); + result = await this._TorLauncherProtocolService.TorSetConfWithReply( + settings, + error + ); } catch (e) { console.log("TorSetConfWithReply error", e); error.details = this._GetSaveSettingsErrorMessage(e.message); @@ -325,6 +334,10 @@ var TorProtocolService = { return this._TorProcessService.TorBootstrapErrorOccurred; },
+ _torBootstrapDebugSetError() { + this._TorProcessService._TorSetBootstrapErrorForDebug(); + }, + // Resolves to null if ok, or an error otherwise async connect() { const kTorConfKeyDisableNetwork = "DisableNetwork"; @@ -396,7 +409,7 @@ class TorBootstrapRequest {
async observe(subject, topic, data) { const obj = subject?.wrappedJSObject; - switch(topic) { + switch (topic) { case TorTopics.BootstrapStatus: { const progress = obj.PROGRESS; const status = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG"); @@ -432,7 +445,9 @@ class TorBootstrapRequest {
// resolves 'true' if bootstrap succeeds, false otherwise async bootstrap() { - if (this._bootstrapPromise) return this._bootstrapPromise; + if (this._bootstrapPromise) { + return this._bootstrapPromise; + }
this._bootstrapPromise = new Promise(async (resolve, reject) => { this._bootstrapPromiseResolve = resolve; @@ -446,7 +461,10 @@ class TorBootstrapRequest { this._timeoutID = setTimeout(async () => { await TorProtocolService.torStopBootstrap(); if (this.onbootstraperror) { - this.onbootstraperror("Tor Bootstrap process timed out", `Bootstrap attempt abandoned after waiting ${this.timeout} ms`); + this.onbootstraperror( + "Tor Bootstrap process timed out", + `Bootstrap attempt abandoned after waiting ${this.timeout} ms` + ); } this._bootstrapPromiseResolve(false); }, this.timeout); @@ -481,4 +499,4 @@ class TorBootstrapRequest {
this._bootstrapPromiseResolve(false); } -}; +} diff --git a/browser/modules/TorSettings.jsm b/browser/modules/TorSettings.jsm index 1b5b564e1e62c..87bc129602930 100644 --- a/browser/modules/TorSettings.jsm +++ b/browser/modules/TorSettings.jsm @@ -195,7 +195,7 @@ let parseAddrPortList = function(aAddrPortList) { return retval; };
-// expects a '/n' or '/r/n' delimited bridge string, which we split and trim +// expects a '\n' or '\r\n' delimited bridge string, which we split and trim // each bridge string can also optionally have 'bridge' at the beginning ie: // bridge $(type) $(address):$(port) $(certificate) // we strip out the 'bridge' prefix here @@ -226,6 +226,10 @@ let parsePortList = function(aPortListString) { };
let getBuiltinBridgeStrings = function(builtinType) { + if (!builtinType) { + return []; + } + let bridgeBranch = Services.prefs.getBranch(TorLauncherPrefs.default_bridge); let bridgeBranchPrefs = bridgeBranch.getChildList(""); let retval = []; @@ -248,7 +252,7 @@ let getBuiltinBridgeStrings = function(builtinType) { return retval; };
-/* Array methods */ +/* Helper methods */
let arrayShuffle = function(array) { // fisher-yates shuffle @@ -356,37 +360,24 @@ const TorSettings = (() => { settings.quickstart.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.quickstart.enabled); /* Bridges */ settings.bridges.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.bridges.enabled); - if (settings.bridges.enabled) { - settings.bridges.source = Services.prefs.getIntPref(TorSettingsPrefs.bridges.source); - // builtin bridge (obfs4, meek, snowlfake, etc) - if (settings.bridges.source == TorBridgeSource.BuiltIn) { - let builtinType = Services.prefs.getStringPref(TorSettingsPrefs.bridges.builtin_type); - settings.bridges.builtin_type = builtinType; - // always dynamically load builtin bridges rather than loading the cached versions - // if the user upgrades and the builtin bridges have changed, we want to ensure the user - // can still bootstrap using the provided bridges - let bridgeStrings = getBuiltinBridgeStrings(builtinType); - if (bridgeStrings.length > 0) { - settings.bridges.bridge_strings = bridgeStrings; - } else { - // in this case the user is using a builtin bridge that is no longer supported, - // reset to settings to default values - settings.bridges.enabled = false; - settings.bridges.source = TorBridgeSource.Invalid; - settings.bridges.builtin_type = null; - } - } else { - settings.bridges.bridge_strings = []; - let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); - bridgeBranchPrefs.forEach(pref => { - let bridgeString = Services.prefs.getStringPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); - settings.bridges.bridge_strings.push(bridgeString); - }); + settings.bridges.source = Services.prefs.getIntPref(TorSettingsPrefs.bridges.source, TorBridgeSource.Invalid); + if (settings.bridges.source == TorBridgeSource.BuiltIn) { + let builtinType = Services.prefs.getStringPref(TorSettingsPrefs.bridges.builtin_type); + settings.bridges.builtin_type = builtinType; + settings.bridges.bridge_strings = getBuiltinBridgeStrings(builtinType); + if (settings.bridges.bridge_strings.length == 0) { + // in this case the user is using a builtin bridge that is no longer supported, + // reset to settings to default values + settings.bridges.source = TorBridgeSource.Invalid; + settings.bridges.builtin_type = null; } } else { - settings.bridges.source = TorBridgeSource.Invalid; - settings.bridges.builtin_type = null; settings.bridges.bridge_strings = []; + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + const bridgeString = Services.prefs.getStringPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + settings.bridges.bridge_strings.push(bridgeString); + }); } /* Proxy */ settings.proxy.enabled = Services.prefs.getBoolPref(TorSettingsPrefs.proxy.enabled); @@ -428,29 +419,18 @@ const TorSettings = (() => { Services.prefs.setBoolPref(TorSettingsPrefs.quickstart.enabled, settings.quickstart.enabled); /* Bridges */ Services.prefs.setBoolPref(TorSettingsPrefs.bridges.enabled, settings.bridges.enabled); - if (settings.bridges.enabled) { - Services.prefs.setIntPref(TorSettingsPrefs.bridges.source, settings.bridges.source); - if (settings.bridges.source === TorBridgeSource.BuiltIn) { - Services.prefs.setStringPref(TorSettingsPrefs.bridges.builtin_type, settings.bridges.builtin_type); - } else { - Services.prefs.clearUserPref(TorSettingsPrefs.bridges.builtin_type); - } - // erase existing bridge strings - let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); - bridgeBranchPrefs.forEach(pref => { - Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); - }); - // write new ones + Services.prefs.setIntPref(TorSettingsPrefs.bridges.source, settings.bridges.source); + Services.prefs.setStringPref(TorSettingsPrefs.bridges.builtin_type, settings.bridges.builtin_type); + // erase existing bridge strings + let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); + bridgeBranchPrefs.forEach(pref => { + Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); + }); + // write new ones + if (settings.bridges.source !== TorBridgeSource.BuiltIn) { settings.bridges.bridge_strings.forEach((string, index) => { Services.prefs.setStringPref(`${TorSettingsPrefs.bridges.bridge_strings}.${index}`, string); }); - } else { - Services.prefs.clearUserPref(TorSettingsPrefs.bridges.source); - Services.prefs.clearUserPref(TorSettingsPrefs.bridges.builtin_type); - let bridgeBranchPrefs = Services.prefs.getBranch(TorSettingsPrefs.bridges.bridge_strings).getChildList(""); - bridgeBranchPrefs.forEach(pref => { - Services.prefs.clearUserPref(`${TorSettingsPrefs.bridges.bridge_strings}${pref}`); - }); } /* Proxy */ Services.prefs.setBoolPref(TorSettingsPrefs.proxy.enabled, settings.proxy.enabled); @@ -488,11 +468,11 @@ const TorSettings = (() => { let settingsMap = new Map();
/* Bridges */ - settingsMap.set(TorConfigKeys.useBridges, settings.bridges.enabled); - if (settings.bridges.enabled) { + const haveBridges = settings.bridges.enabled && settings.bridges.bridge_strings.length > 0; + settingsMap.set(TorConfigKeys.useBridges, haveBridges); + if (haveBridges) { settingsMap.set(TorConfigKeys.bridgeList, settings.bridges.bridge_strings); } else { - // shuffle bridge list settingsMap.set(TorConfigKeys.bridgeList, null); }
@@ -545,29 +525,28 @@ const TorSettings = (() => { let backup = this.getSettings();
try { - if (settings.bridges.enabled) { - this._settings.bridges.enabled = true; - this._settings.bridges.source = settings.bridges.source; - switch(settings.bridges.source) { - case TorBridgeSource.BridgeDB: - case TorBridgeSource.UserProvided: - this._settings.bridges.bridge_strings = settings.bridges.bridge_strings - break; - case TorBridgeSource.BuiltIn: { - this._settings.bridges.builtin_type = settings.bridges.builtin_type; - let bridgeStrings = getBuiltinBridgeStrings(settings.bridges.builtin_type); - if (bridgeStrings.length > 0) { - this._settings.bridges.bridge_strings = bridgeStrings; - } else { - throw new Error(`No available builtin bridges of type ${settings.bridges.builtin_type}`); - } - break; + this._settings.bridges.enabled = !!settings.bridges.enabled; + this._settings.bridges.source = settings.bridges.source; + switch(settings.bridges.source) { + case TorBridgeSource.BridgeDB: + case TorBridgeSource.UserProvided: + this._settings.bridges.bridge_strings = settings.bridges.bridge_strings; + break; + case TorBridgeSource.BuiltIn: { + this._settings.bridges.builtin_type = settings.bridges.builtin_type; + settings.bridges.bridge_strings = getBuiltinBridgeStrings(settings.bridges.builtin_type); + if (settings.bridges.bridge_strings.length == 0 && settings.bridges.enabled) { + throw new Error(`No available builtin bridges of type ${settings.bridges.builtin_type}`); } - default: - throw new Error(`Bridge source '${settings.source}' is not a valid source`); + break; } - } else { - this.bridges.enabled = false; + case TorBridgeSource.Invalid: + break; + default: + if (settings.bridges.enabled) { + throw new Error(`Bridge source '${settings.source}' is not a valid source`); + } + break; }
// TODO: proxy and firewall @@ -609,19 +588,20 @@ const TorSettings = (() => { get enabled() { return self._settings.bridges.enabled; }, set enabled(val) { self._settings.bridges.enabled = val; - // reset bridge settings - self._settings.bridges.source = TorBridgeSource.Invalid; - self._settings.bridges.builtin_type = null; - self._settings.bridges.bridge_strings = []; }, get source() { return self._settings.bridges.source; }, set source(val) { self._settings.bridges.source = val; }, get builtin_type() { return self._settings.bridges.builtin_type; }, set builtin_type(val) { - let bridgeStrings = getBuiltinBridgeStrings(val); + const bridgeStrings = getBuiltinBridgeStrings(val); if (bridgeStrings.length > 0) { self._settings.bridges.builtin_type = val; self._settings.bridges.bridge_strings = bridgeStrings; + } else { + self._settings.bridges.builtin_type = ""; + if (self._settings.bridges.source === TorBridgeSource.BuiltIn) { + self._settings.bridges.source = TorBridgeSource.Invalid; + } } }, get bridge_strings() { return arrayCopy(self._settings.bridges.bridge_strings); },
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
commit 207037ff0a0f29a5c152e76649bc4024466be940 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Thu Feb 10 10:35:38 2022 +0100
squash! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Bug 40774: Update about:preferences page to match new UI designs --- browser/components/preferences/preferences.js | 10 +- browser/components/preferences/preferences.xhtml | 5 +- .../torpreferences/content/bridgeQrDialog.jsm | 51 + .../torpreferences/content/bridgeQrDialog.xhtml | 23 + .../torpreferences/content/builtinBridgeDialog.jsm | 142 +++ .../content/builtinBridgeDialog.xhtml | 43 + ...gory.inc.xhtml => connectionCategory.inc.xhtml} | 8 +- .../torpreferences/content/connectionPane.js | 1315 ++++++++++++++++++++ .../torpreferences/content/connectionPane.xhtml | 177 +++ .../content/connectionSettingsDialog.jsm | 393 ++++++ .../content/connectionSettingsDialog.xhtml | 62 + .../components/torpreferences/content/network.svg | 6 + .../torpreferences/content/provideBridgeDialog.jsm | 69 + .../content/provideBridgeDialog.xhtml | 21 + .../torpreferences/content/requestBridgeDialog.jsm | 32 +- .../content/requestBridgeDialog.xhtml | 10 +- .../torpreferences/content/torLogDialog.jsm | 18 + .../components/torpreferences/content/torPane.js | 940 -------------- .../torpreferences/content/torPane.xhtml | 157 --- .../torpreferences/content/torPreferences.css | 394 +++++- browser/components/torpreferences/jar.mn | 15 +- 21 files changed, 2739 insertions(+), 1152 deletions(-)
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js index ce338584142ed..5981bcd38fc83 100644 --- a/browser/components/preferences/preferences.js +++ b/browser/components/preferences/preferences.js @@ -13,7 +13,7 @@ /* import-globals-from findInPage.js */ /* import-globals-from ../../base/content/utilityOverlay.js */ /* import-globals-from ../../../toolkit/content/preferencesBindings.js */ -/* import-globals-from ../torpreferences/content/torPane.js */ +/* import-globals-from ../torpreferences/content/connectionPane.js */
"use strict";
@@ -137,12 +137,12 @@ function init_all() { register_module("paneSync", gSyncPane); } register_module("paneSearchResults", gSearchResultsPane); - if (gTorPane.enabled) { - document.getElementById("category-tor").hidden = false; - register_module("paneTor", gTorPane); + 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-paneTor").remove(); + document.getElementById("template-paneConnection").remove(); }
gSearchResultsPane.init(); diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml index 07ab5cc7b626d..30ce70079adb7 100644 --- a/browser/components/preferences/preferences.xhtml +++ b/browser/components/preferences/preferences.xhtml @@ -6,6 +6,7 @@ <?xml-stylesheet href="chrome://global/skin/global.css"?>
<?xml-stylesheet href="chrome://global/skin/in-content/common.css"?> +<?xml-stylesheet href="chrome://global/skin/in-content/toggle-button.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> <?xml-stylesheet href="chrome://browser/content/preferences/dialogs/handlers.css"?> <?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?> @@ -158,7 +159,7 @@ <label class="category-name" flex="1" data-l10n-id="pane-experimental-title"></label> </richlistitem>
-#include ../torpreferences/content/torCategory.inc.xhtml +#include ../torpreferences/content/connectionCategory.inc.xhtml
</richlistbox>
@@ -213,7 +214,7 @@ #include containers.inc.xhtml #include sync.inc.xhtml #include experimental.inc.xhtml -#include ../torpreferences/content/torPane.xhtml +#include ../torpreferences/content/connectionPane.xhtml </vbox> </vbox> </vbox> diff --git a/browser/components/torpreferences/content/bridgeQrDialog.jsm b/browser/components/torpreferences/content/bridgeQrDialog.jsm new file mode 100644 index 0000000000000..e63347742ea50 --- /dev/null +++ b/browser/components/torpreferences/content/bridgeQrDialog.jsm @@ -0,0 +1,51 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["BridgeQrDialog"]; + +const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm"); + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +class BridgeQrDialog { + constructor() { + this._bridgeString = ""; + } + + static get selectors() { + return { + target: "#bridgeQr-target", + }; + } + + _populateXUL(window, dialog) { + dialog.parentElement.setAttribute("title", TorStrings.settings.scanQrTitle); + const target = dialog.querySelector(BridgeQrDialog.selectors.target); + const style = window.getComputedStyle(target); + const width = style.width.substr(0, style.width.length - 2); + const height = style.height.substr(0, style.height.length - 2); + new QRCode(target, { + text: this._bridgeString, + width, + height, + colorDark: style.color, + colorLight: style.backgroundColor, + document: window.document, + }); + } + + init(window, dialog) { + // Defer to later until Firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, dialog); + }, 0); + } + + openDialog(gSubDialog, bridgeString) { + this._bridgeString = bridgeString; + gSubDialog.open( + "chrome://browser/content/torpreferences/bridgeQrDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/bridgeQrDialog.xhtml b/browser/components/torpreferences/content/bridgeQrDialog.xhtml new file mode 100644 index 0000000000000..2a49e4c0e7d9e --- /dev/null +++ b/browser/components/torpreferences/content/bridgeQrDialog.xhtml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="bridgeQr-dialog" buttons="accept"> + <html:div id="bridgeQr-container"> + <html:div id="bridgeQr-target"/> + <html:div id="bridgeQr-onionBox"/> + <html:div id="bridgeQr-onion"/> + </html:div> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let dialogObject = window.arguments[0]; + let dialogElement = document.getElementById("bridgeQr-dialog"); + dialogObject.init(window, dialogElement); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.jsm b/browser/components/torpreferences/content/builtinBridgeDialog.jsm new file mode 100644 index 0000000000000..1d4dda8f5ca9c --- /dev/null +++ b/browser/components/torpreferences/content/builtinBridgeDialog.jsm @@ -0,0 +1,142 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["BuiltinBridgeDialog"]; + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +const { + TorSettings, + TorBridgeSource, + TorBuiltinBridgeTypes, +} = ChromeUtils.import("resource:///modules/TorSettings.jsm"); + +class BuiltinBridgeDialog { + constructor() { + this._dialog = null; + this._bridgeType = ""; + this._windowPadding = 0; + } + + static get selectors() { + return { + header: "#torPreferences-builtinBridge-header", + description: "#torPreferences-builtinBridge-description", + radiogroup: "#torPreferences-builtinBridge-typeSelection", + obfsRadio: "#torPreferences-builtinBridges-radioObfs", + obfsDescr: "#torPreferences-builtinBridges-descrObfs", + snowflakeRadio: "#torPreferences-builtinBridges-radioSnowflake", + snowflakeDescr: "#torPreferences-builtinBridges-descrSnowflake", + meekAzureRadio: "#torPreferences-builtinBridges-radioMeekAzure", + meekAzureDescr: "#torPreferences-builtinBridges-descrMeekAzure", + }; + } + + _populateXUL(window, aDialog) { + const selectors = BuiltinBridgeDialog.selectors; + + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + { + dialogWin.setAttribute("title", TorStrings.settings.builtinBridgeTitle); + let windowStyle = window.getComputedStyle(dialogWin); + this._windowPadding = + parseFloat(windowStyle.paddingLeft) + + parseFloat(windowStyle.paddingRight); + } + const initialWidth = dialogWin.clientWidth - this._windowPadding; + + this._dialog.querySelector(selectors.header).textContent = + TorStrings.settings.builtinBridgeHeader; + this._dialog.querySelector(selectors.description).textContent = + TorStrings.settings.builtinBridgeDescription; + let radioGroup = this._dialog.querySelector(selectors.radiogroup); + + let types = { + obfs4: { + elemRadio: this._dialog.querySelector(selectors.obfsRadio), + elemDescr: this._dialog.querySelector(selectors.obfsDescr), + label: TorStrings.settings.builtinBridgeObfs4, + descr: TorStrings.settings.builtinBridgeObfs4Description, + }, + snowflake: { + elemRadio: this._dialog.querySelector(selectors.snowflakeRadio), + elemDescr: this._dialog.querySelector(selectors.snowflakeDescr), + label: TorStrings.settings.builtinBridgeSnowflake, + descr: TorStrings.settings.builtinBridgeSnowflakeDescription, + }, + "meek-azure": { + elemRadio: this._dialog.querySelector(selectors.meekAzureRadio), + elemDescr: this._dialog.querySelector(selectors.meekAzureDescr), + label: TorStrings.settings.builtinBridgeMeekAzure, + descr: TorStrings.settings.builtinBridgeMeekAzureDescription, + }, + }; + + TorBuiltinBridgeTypes.forEach(type => { + types[type].elemRadio.parentElement.setAttribute("hidden", "false"); + types[type].elemDescr.parentElement.setAttribute("hidden", "false"); + types[type].elemRadio.setAttribute("label", types[type].label); + types[type].elemDescr.textContent = types[type].descr; + }); + + if ( + TorSettings.bridges.enabled && + TorSettings.bridges.source == TorBridgeSource.BuiltIn + ) { + radioGroup.selectedItem = + types[TorSettings.bridges.builtin_type]?.elemRadio; + this._bridgeType = TorSettings.bridges.builtin_type; + } else { + radioGroup.selectedItem = null; + this._bridgeType = ""; + } + + // Use the initial width, because the window is expanded when we add texts + this.resized(initialWidth); + + this._dialog.addEventListener("dialogaccept", e => { + this._bridgeType = radioGroup.value; + }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + "https://tb-manual.torproject.org/circumvention/", + "tab" + ); + }); + } + + resized(width) { + if (this._dialog === null) { + return; + } + const dialogWin = this._dialog.parentElement; + if (width === undefined) { + width = dialogWin.clientWidth - this._windowPadding; + } + let windowPos = dialogWin.getBoundingClientRect(); + dialogWin.querySelectorAll("div").forEach(div => { + let divPos = div.getBoundingClientRect(); + div.style.width = width - (divPos.left - windowPos.left) + "px"; + }); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, aDialog); + }, 0); + } + + openDialog(gSubDialog, aCloseCallback) { + gSubDialog.open( + "chrome://browser/content/torpreferences/builtinBridgeDialog.xhtml", + { + features: "resizable=yes", + closingCallback: () => { + aCloseCallback(this._bridgeType); + }, + }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/builtinBridgeDialog.xhtml b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml new file mode 100644 index 0000000000000..c1bf202ca1be5 --- /dev/null +++ b/browser/components/torpreferences/content/builtinBridgeDialog.xhtml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-builtinBridge-dialog" + buttons="help,accept,cancel"> + <html:h3 id="torPreferences-builtinBridge-header">​</html:h3> + <description> + <html:div id="torPreferences-builtinBridge-description">​<br/>​</html:div> + </description> + <radiogroup id="torPreferences-builtinBridge-typeSelection"> + <hbox hidden="true"> + <radio id="torPreferences-builtinBridges-radioObfs" value="obfs4"/> + </hbox> + <hbox hidden="true" class="indent"> + <html:div id="torPreferences-builtinBridges-descrObfs"></html:div> + </hbox> + <hbox hidden="true"> + <radio id="torPreferences-builtinBridges-radioSnowflake" value="snowflake"/> + </hbox> + <hbox hidden="true" class="indent"> + <html:div id="torPreferences-builtinBridges-descrSnowflake"></html:div> + </hbox> + <hbox hidden="true"> + <radio id="torPreferences-builtinBridges-radioMeekAzure" value="meek-azure"/> + </hbox> + <hbox hidden="true" class="indent"> + <html:div id="torPreferences-builtinBridges-descrMeekAzure"></html:div> + </hbox> + </radiogroup> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let builtinBridgeDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-builtinBridge-dialog"); + builtinBridgeDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/torCategory.inc.xhtml b/browser/components/torpreferences/content/connectionCategory.inc.xhtml similarity index 57% rename from browser/components/torpreferences/content/torCategory.inc.xhtml rename to browser/components/torpreferences/content/connectionCategory.inc.xhtml index abe56200f571b..15cf24cfe6950 100644 --- a/browser/components/torpreferences/content/torCategory.inc.xhtml +++ b/browser/components/torpreferences/content/connectionCategory.inc.xhtml @@ -1,9 +1,9 @@ -<richlistitem id="category-tor" +<richlistitem id="category-connection" class="category" - value="paneTor" - helpTopic="prefs-tor" + value="paneConnection" + helpTopic="prefs-connection" align="center" hidden="true"> <image class="category-icon"/> - <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Tor"/> + <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Connection"/> </richlistitem> diff --git a/browser/components/torpreferences/content/connectionPane.js b/browser/components/torpreferences/content/connectionPane.js new file mode 100644 index 0000000000000..309d6498a0c80 --- /dev/null +++ b/browser/components/torpreferences/content/connectionPane.js @@ -0,0 +1,1315 @@ +"use strict"; + +/* global Services, gSubDialog */ + +const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + +const { + TorSettings, + TorSettingsTopics, + TorSettingsData, + TorBridgeSource, +} = ChromeUtils.import("resource:///modules/TorSettings.jsm"); + +const { TorProtocolService } = ChromeUtils.import( + "resource:///modules/TorProtocolService.jsm" +); + +const { + TorConnect, + TorConnectTopics, + TorConnectState, + TorCensorshipLevel, +} = ChromeUtils.import("resource:///modules/TorConnect.jsm"); + +const { TorLogDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/torLogDialog.jsm" +); + +const { ConnectionSettingsDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/connectionSettingsDialog.jsm" +); + +const { BridgeQrDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/bridgeQrDialog.jsm" +); + +const { BuiltinBridgeDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/builtinBridgeDialog.jsm" +); + +const { RequestBridgeDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/requestBridgeDialog.jsm" +); + +const { ProvideBridgeDialog } = ChromeUtils.import( + "chrome://browser/content/torpreferences/provideBridgeDialog.jsm" +); + +const { MoatRPC } = ChromeUtils.import("resource:///modules/Moat.jsm"); + +const { QRCode } = ChromeUtils.import("resource://gre/modules/QRCode.jsm"); + +ChromeUtils.defineModuleGetter( + this, + "TorStrings", + "resource:///modules/TorStrings.jsm" +); + +const InternetStatus = Object.freeze({ + Unknown: 0, + Online: 1, + Offline: -1, +}); + +/* + 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 = { + category: { + title: "label#torPreferences-labelCategory", + }, + messageBox: { + box: "div#torPreferences-connectMessageBox", + message: "td#torPreferences-connectMessageBox-message", + button: "button#torPreferences-connectMessageBox-button", + }, + torPreferences: { + header: "h1#torPreferences-header", + description: "span#torPreferences-description", + learnMore: "label#torPreferences-learnMore", + }, + status: { + internetLabel: "#torPreferences-status-internet-label", + internetTest: "#torPreferences-status-internet-test", + internetIcon: "#torPreferences-status-internet-statusIcon", + internetStatus: "#torPreferences-status-internet-status", + torLabel: "#torPreferences-status-tor-label", + torIcon: "#torPreferences-status-tor-statusIcon", + torStatus: "#torPreferences-status-tor-status", + }, + quickstart: { + header: "h2#torPreferences-quickstart-header", + description: "span#torPreferences-quickstart-description", + enableQuickstartCheckbox: "checkbox#torPreferences-quickstart-toggle", + }, + bridges: { + header: "h1#torPreferences-bridges-header", + description: "span#torPreferences-bridges-description", + learnMore: "label#torPreferences-bridges-learnMore", + locationGroup: "#torPreferences-bridges-locationGroup", + locationLabel: "#torPreferences-bridges-locationLabel", + location: "#torPreferences-bridges-location", + locationEntries: "#torPreferences-bridges-locationEntries", + chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe", + currentHeader: "#torPreferences-currentBridges-header", + currentHeaderText: "#torPreferences-currentBridges-headerText", + switch: "#torPreferences-currentBridges-switch", + cards: "#torPreferences-currentBridges-cards", + cardTemplate: "#torPreferences-bridgeCard-template", + card: ".torPreferences-bridgeCard", + cardId: ".torPreferences-bridgeCard-id", + cardHeadingAddr: ".torPreferences-bridgeCard-headingAddr", + cardConnectedLabel: ".torPreferences-bridgeCard-connectedLabel", + cardOptions: ".torPreferences-bridgeCard-options", + cardMenu: "#torPreferences-bridgeCard-menu", + cardQrGrid: ".torPreferences-bridgeCard-grid", + cardQrContainer: ".torPreferences-bridgeCard-qr", + cardQr: ".torPreferences-bridgeCard-qrCode", + cardShare: ".torPreferences-bridgeCard-share", + cardAddr: ".torPreferences-bridgeCard-addr", + cardLearnMore: ".torPreferences-bridgeCard-learnMore", + cardCopy: ".torPreferences-bridgeCard-copyButton", + showAll: "#torPreferences-currentBridges-showAll", + removeAll: "#torPreferences-currentBridges-removeAll", + addHeader: "#torPreferences-addBridge-header", + addBuiltinLabel: "#torPreferences-addBridge-labelBuiltinBridge", + addBuiltinButton: "#torPreferences-addBridge-buttonBuiltinBridge", + requestLabel: "#torPreferences-addBridge-labelRequestBridge", + requestButton: "#torPreferences-addBridge-buttonRequestBridge", + enterLabel: "#torPreferences-addBridge-labelEnterBridge", + enterButton: "#torPreferences-addBridge-buttonEnterBridge", + }, + advanced: { + header: "h1#torPreferences-advanced-header", + label: "#torPreferences-advanced-label", + button: "#torPreferences-advanced-button", + torLogsLabel: "label#torPreferences-torLogs", + torLogsButton: "button#torPreferences-buttonTorLogs", + }, + }; /* selectors */ + + let retval = { + // cached frequently accessed DOM elements + _enableQuickstartCheckbox: null, + + _internetStatus: InternetStatus.Unknown, + + _controller: null, + + _currentBridge: "", + + // disables the provided list of elements + _setElementsDisabled(elements, disabled) { + for (let currentElement of elements) { + currentElement.disabled = disabled; + } + }, + + // populate xul with strings and cache the relevant elements + _populateXUL() { + // saves tor settings to disk when navigate away from about:preferences + window.addEventListener("blur", val => { + TorProtocolService.flushSettings(); + }); + + document + .querySelector(selectors.category.title) + .setAttribute("value", TorStrings.settings.categoryTitle); + + let prefpane = document.getElementById("mainPrefPane"); + + // 'Connect to Tor' Message Bar + + const messageBox = prefpane.querySelector(selectors.messageBox.box); + const messageBoxMessage = prefpane.querySelector( + selectors.messageBox.message + ); + const messageBoxButton = prefpane.querySelector( + selectors.messageBox.button + ); + // wire up connect button + messageBoxButton.addEventListener("click", () => { + TorConnect.beginBootstrap(); + TorConnect.openTorConnect(); + }); + + this._populateMessagebox = () => { + if ( + TorConnect.shouldShowTorConnect && + TorConnect.state === TorConnectState.Configuring + ) { + // set messagebox style and text + if (TorProtocolService.torBootstrapErrorOccurred()) { + messageBox.parentNode.style.display = null; + messageBox.className = "error"; + messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage; + messageBoxButton.innerText = TorStrings.torConnect.tryAgain; + } else { + messageBox.parentNode.style.display = null; + messageBox.className = "warning"; + messageBoxMessage.innerText = TorStrings.torConnect.connectMessage; + messageBoxButton.innerText = TorStrings.torConnect.torConnectButton; + } + } else { + // we need to explicitly hide the groupbox, as switching between + // the tor pane and other panes will 'unhide' (via the 'hidden' + // attribute) the groupbox, offsetting all of the content down + // by the groupbox's margin (even if content is 0 height) + messageBox.parentNode.style.display = "none"; + messageBox.className = "hidden"; + messageBoxMessage.innerText = ""; + messageBoxButton.innerText = ""; + } + }; + this._populateMessagebox(); + + // Heading + prefpane.querySelector(selectors.torPreferences.header).innerText = + TorStrings.settings.categoryTitle; + prefpane.querySelector(selectors.torPreferences.description).textContent = + TorStrings.settings.torPreferencesDescription; + { + let learnMore = prefpane.querySelector( + selectors.torPreferences.learnMore + ); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute( + "href", + TorStrings.settings.learnMoreTorBrowserURL + ); + } + + // Internet and Tor status + prefpane.querySelector(selectors.status.internetLabel).textContent = + TorStrings.settings.statusInternetLabel; + prefpane.querySelector(selectors.status.torLabel).textContent = + TorStrings.settings.statusTorLabel; + const internetTest = prefpane.querySelector( + selectors.status.internetTest + ); + internetTest.setAttribute( + "label", + TorStrings.settings.statusInternetTest + ); + internetTest.addEventListener("command", async () => { + this.onInternetTest(); + }); + const internetIcon = prefpane.querySelector( + selectors.status.internetIcon + ); + const internetStatus = prefpane.querySelector( + selectors.status.internetStatus + ); + const torIcon = prefpane.querySelector(selectors.status.torIcon); + const torStatus = prefpane.querySelector(selectors.status.torStatus); + this._populateStatus = () => { + switch (this._internetStatus) { + case InternetStatus.Unknown: + internetTest.removeAttribute("hidden"); + break; + case InternetStatus.Online: + internetTest.setAttribute("hidden", "true"); + internetIcon.className = "online"; + internetStatus.textContent = + TorStrings.settings.statusInternetOnline; + break; + case InternetStatus.Offline: + internetTest.setAttribute("hidden", "true"); + internetIcon.className = "offline"; + internetStatus.textContent = + TorStrings.settings.statusInternetOffline; + break; + } + if (TorConnect.state === TorConnectState.Bootstrapped) { + torIcon.className = "connected"; + torStatus.textContent = TorStrings.settings.statusTorConnected; + } else if ( + TorConnect.detectedCensorshipLevel > TorCensorshipLevel.None + ) { + torIcon.className = "blocked"; + torStatus.textContent = TorStrings.settings.statusTorBlocked; + } else { + torIcon.className = ""; + torStatus.textContent = TorStrings.settings.statusTorNotConnected; + } + }; + this._populateStatus(); + + // Quickstart + prefpane.querySelector(selectors.quickstart.header).innerText = + TorStrings.settings.quickstartHeading; + prefpane.querySelector(selectors.quickstart.description).textContent = + TorStrings.settings.quickstartDescription; + + this._enableQuickstartCheckbox = prefpane.querySelector( + selectors.quickstart.enableQuickstartCheckbox + ); + this._enableQuickstartCheckbox.setAttribute( + "label", + TorStrings.settings.quickstartCheckbox + ); + this._enableQuickstartCheckbox.addEventListener("command", e => { + const checked = this._enableQuickstartCheckbox.checked; + TorSettings.quickstart.enabled = checked; + TorSettings.saveToPrefs().applySettings(); + }); + this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled; + Services.obs.addObserver(this, TorSettingsTopics.SettingChanged); + + // Bridge setup + prefpane.querySelector(selectors.bridges.header).innerText = + TorStrings.settings.bridgesHeading; + prefpane.querySelector(selectors.bridges.description).textContent = + TorStrings.settings.bridgesDescription; + { + let learnMore = prefpane.querySelector(selectors.bridges.learnMore); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL); + } + + // Location + { + 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", e => { + TorConnect.beginAutoBootstrap(location.value); + }); + this._populateLocations = () => { + let value = location.value; + locationEntries.textContent = ""; + + { + const item = document.createXULElement("menuitem"); + item.setAttribute("value", ""); + item.setAttribute( + "label", + TorStrings.settings.bridgeLocationAutomatic + ); + locationEntries.appendChild(item); + } + + const codes = TorConnect.countryCodes; + const items = codes.map(code => { + const item = document.createXULElement("menuitem"); + item.setAttribute("value", code); + item.setAttribute( + "label", + TorConnect.countryNames[code] + ? TorConnect.countryNames[code] + : code + ); + return item; + }); + items.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + locationEntries.append(...items); + location.value = value; + }; + this._showAutoconfiguration = () => { + if ( + !TorConnect.shouldShowTorConnect || + !TorProtocolService.torBootstrapErrorOccurred() + ) { + locationGroup.setAttribute("hidden", "true"); + return; + } + // Populate locations, even though we will show only the automatic + // item for a moment. In my opinion showing the button immediately is + // better then waiting for the Moat query to finish (after a while) + // and showing the controls only after that. + this._populateLocations(); + locationGroup.removeAttribute("hidden"); + if (!TorConnect.countryCodes.length) { + TorConnect.getCountryCodes().then(() => this._populateLocations()); + } + }; + this._showAutoconfiguration(); + } + + // Bridge cards + const bridgeHeader = prefpane.querySelector( + selectors.bridges.currentHeader + ); + bridgeHeader.querySelector( + selectors.bridges.currentHeaderText + ).textContent = TorStrings.settings.bridgeCurrent; + const bridgeSwitch = bridgeHeader.querySelector(selectors.bridges.switch); + bridgeSwitch.addEventListener("change", () => { + TorSettings.bridges.enabled = bridgeSwitch.checked; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + const bridgeTemplate = prefpane.querySelector( + selectors.bridges.cardTemplate + ); + { + const learnMore = bridgeTemplate.querySelector( + selectors.bridges.cardLearnMore + ); + learnMore.setAttribute("value", TorStrings.settings.learnMore); + learnMore.setAttribute("href", "about:blank"); + } + bridgeTemplate.querySelector( + selectors.bridges.cardConnectedLabel + ).textContent = TorStrings.settings.statusTorConnected; + bridgeTemplate + .querySelector(selectors.bridges.cardCopy) + .setAttribute("label", TorStrings.settings.bridgeCopy); + bridgeTemplate.querySelector(selectors.bridges.cardShare).textContent = + TorStrings.settings.bridgeShare; + const bridgeCards = prefpane.querySelector(selectors.bridges.cards); + const bridgeMenu = prefpane.querySelector(selectors.bridges.cardMenu); + + this._addBridgeCard = bridgeString => { + const card = bridgeTemplate.cloneNode(true); + card.removeAttribute("id"); + const grid = card.querySelector(selectors.bridges.cardQrGrid); + card.addEventListener("click", e => { + let target = e.target; + let apply = true; + while (target !== null && target !== card && apply) { + // Deal with mixture of "command" and "click" events + apply = !target.classList?.contains("stop-click"); + target = target.parentElement; + } + if (apply) { + if (card.classList.toggle("expanded")) { + grid.classList.add("to-animate"); + grid.style.height = `${grid.scrollHeight}px`; + } else { + // Be sure we still have the to-animate class + grid.classList.add("to-animate"); + grid.style.height = ""; + } + } + }); + const emojis = makeBridgeId(bridgeString).map(e => { + const span = document.createElement("span"); + span.className = "emoji"; + span.textContent = e; + return span; + }); + const idString = TorStrings.settings.bridgeId; + const id = card.querySelector(selectors.bridges.cardId); + const details = parseBridgeLine(bridgeString); + if (details && details.id !== undefined) { + card.setAttribute("data-bridge-id", details.id); + } + // TODO: properly handle "vanilla" bridges? + const type = + details && details.transport !== undefined + ? details.transport + : "vanilla"; + for (const piece of idString.split(/(#[12])/)) { + if (piece == "#1") { + id.append(type); + } else if (piece == "#2") { + id.append(...emojis); + } else { + id.append(piece); + } + } + card.querySelector( + selectors.bridges.cardHeadingAddr + ).textContent = bridgeString; + const optionsButton = card.querySelector(selectors.bridges.cardOptions); + if (TorSettings.bridges.source === TorBridgeSource.BuiltIn) { + optionsButton.setAttribute("hidden", "true"); + } else { + // Cloning the menupopup element does not work as expected. + // Therefore, we use only one, and just before opening it, we remove + // its previous items, and add the ones relative to the bridge whose + // button has been pressed. + optionsButton.addEventListener("click", () => { + const menuItem = document.createXULElement("menuitem"); + menuItem.setAttribute("label", TorStrings.settings.remove); + menuItem.classList.add("menuitem-iconic"); + menuItem.image = "chrome://global/skin/icons/delete.svg"; + menuItem.addEventListener("command", e => { + const strings = TorSettings.bridges.bridge_strings; + const index = strings.indexOf(bridgeString); + if (index !== -1) { + strings.splice(index, 1); + } + TorSettings.bridges.enabled = + bridgeSwitch.checked && !!strings.length; + TorSettings.bridges.bridge_strings = strings.join("\n"); + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + if (bridgeMenu.firstChild) { + bridgeMenu.firstChild.remove(); + } + bridgeMenu.append(menuItem); + bridgeMenu.openPopup(optionsButton, { + position: "bottomleft topleft", + }); + }); + } + const bridgeAddr = card.querySelector(selectors.bridges.cardAddr); + bridgeAddr.setAttribute("value", bridgeString); + const bridgeCopy = card.querySelector(selectors.bridges.cardCopy); + let restoreTimeout = null; + bridgeCopy.addEventListener("command", e => { + this.onCopyBridgeAddress(bridgeAddr); + const label = bridgeCopy.querySelector("label"); + label.setAttribute("value", TorStrings.settings.copied); + bridgeCopy.classList.add("primary"); + + const RESTORE_TIME = 1200; + if (restoreTimeout !== null) { + clearTimeout(restoreTimeout); + } + restoreTimeout = setTimeout(() => { + label.setAttribute("value", TorStrings.settings.bridgeCopy); + bridgeCopy.classList.remove("primary"); + restoreTimeout = null; + }, RESTORE_TIME); + }); + if (details && details.id === this._currentBridge) { + card.classList.add("currently-connected"); + bridgeCards.prepend(card); + } else { + bridgeCards.append(card); + } + // Add the QR only after appending the card, to have the computed style + try { + const container = card.querySelector(selectors.bridges.cardQr); + const style = getComputedStyle(container); + const width = style.width.substr(0, style.width.length - 2); + const height = style.height.substr(0, style.height.length - 2); + new QRCode(container, { + text: bridgeString, + width, + height, + colorDark: style.color, + colorLight: style.backgroundColor, + document, + }); + container.parentElement.addEventListener("click", () => { + this.onShowQr(bridgeString); + }); + } catch (err) { + // TODO: Add a generic image in case of errors such as code overflow. + // It should never happen with correct codes, but after all this + // content can be generated by users... + console.error("Could not generate the QR code for the bridge:", err); + } + }; + this._checkBridgeCardsHeight = () => { + for (const card of bridgeCards.children) { + // Expanded cards have the height set manually to their details for + // the CSS animation. However, when resizing the window, we may need + // to adjust their height. + if (card.classList.contains("expanded")) { + const grid = card.querySelector(selectors.bridges.cardQrGrid); + // Reset it first, to avoid having a height that is higher than + // strictly needed. Also, remove the to-animate class, because the + // animation interferes with this process! + grid.classList.remove("to-animate"); + grid.style.height = ""; + grid.style.height = `${grid.scrollHeight}px`; + } + } + }; + this._currentBridgesExpanded = false; + const showAll = prefpane.querySelector(selectors.bridges.showAll); + showAll.setAttribute("label", TorStrings.settings.bridgeShowAll); + showAll.addEventListener("command", () => { + this._currentBridgesExpanded = true; + this._populateBridgeCards(); + }); + const removeAll = prefpane.querySelector(selectors.bridges.removeAll); + removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll); + removeAll.addEventListener("command", () => { + this.onRemoveAllBridges(); + }); + this._populateBridgeCards = async () => { + const collapseThreshold = 4; + + let newStrings = new Set(TorSettings.bridges.bridge_strings); + const numBridges = newStrings.size; + if (!newStrings.size) { + bridgeHeader.setAttribute("hidden", "true"); + bridgeCards.setAttribute("hidden", "true"); + showAll.setAttribute("hidden", "true"); + removeAll.setAttribute("hidden", "true"); + bridgeCards.textContent = ""; + return; + } + bridgeHeader.removeAttribute("hidden"); + bridgeCards.removeAttribute("hidden"); + bridgeSwitch.checked = TorSettings.bridges.enabled; + bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled); + + let shownCards = 0; + const toShow = this._currentBridgesExpanded + ? numBridges + : collapseThreshold; + + // Do not remove all the old cards, because it makes scrollbar "jump" + const currentCards = bridgeCards.querySelectorAll( + selectors.bridges.card + ); + for (const card of currentCards) { + const string = card.querySelector(selectors.bridges.cardAddr).value; + const hadString = newStrings.delete(string); + if (!hadString || shownCards == toShow) { + card.remove(); + } else { + shownCards++; + } + } + + // Add only the new strings that remained in the set + for (const bridge of newStrings) { + if (shownCards >= toShow) { + if (this._currentBridge === "") { + break; + } else if (!bridge.includes(this._currentBridge)) { + continue; + } + } + this._addBridgeCard(bridge); + shownCards++; + } + + // If we know the connected bridge, we may have added more than the ones + // we should actually show (but the connected ones have been prepended, + // if needed). So, remove any exceeding ones. + while (shownCards > toShow) { + bridgeCards.lastElementChild.remove(); + shownCards--; + } + + // And finally update the buttons + if (numBridges > collapseThreshold && !this._currentBridgesExpanded) { + showAll.removeAttribute("hidden"); + if (TorSettings.bridges.enabled) { + showAll.classList.add("primary"); + } else { + showAll.classList.remove("primary"); + } + removeAll.setAttribute("hidden", "true"); + if (TorSettings.bridges.enabled) { + // We do not want both collapsed and disabled at the same time, + // because we use collapsed only to display a gradient on the list. + bridgeCards.classList.add("list-collapsed"); + } + } else { + showAll.setAttribute("hidden", "true"); + removeAll.removeAttribute("hidden"); + bridgeCards.classList.remove("list-collapsed"); + } + }; + this._populateBridgeCards(); + this._updateConnectedBridges = () => { + for (const card of bridgeCards.querySelectorAll( + ".currently-connected" + )) { + card.classList.remove("currently-connected"); + } + if (this._currentBridge === "") { + return; + } + // Make sure we have the connected bridge in the list + this._populateBridgeCards(); + // At the moment, IDs do not have to be unique (and it is a concrete + // case also with built-in bridges!). E.g., one line for the IPv4 + // address and one for the IPv6 address, so use querySelectorAll + const cards = bridgeCards.querySelectorAll( + `[data-bridge-id="${this._currentBridge}"]` + ); + for (const card of cards) { + card.classList.add("currently-connected"); + } + const placeholder = document.createElement("span"); + bridgeCards.prepend(placeholder); + placeholder.replaceWith(...cards); + }; + try { + const { controller } = ChromeUtils.import( + "resource://torbutton/modules/tor-control-port.js", + {} + ); + // Avoid the cache because we set our custom event watcher, and at the + // moment, watchers cannot be removed from a controller. + controller(true).then(aController => { + this._controller = aController; + // Getting the circuits may be enough, if we have bootstrapped for a + // while, but at the beginning it gives many bridges as connected, + // because tor pokes all the bridges to find the best one. + // Also, watching circuit events does not work, at the moment, but in + // any case, checking the stream has the advantage that we can see if + // it really used for a connection, rather than tor having created + // this circuit to check if the bridge can be used. We do this by + // checking if the stream has SOCKS username, which actually contains + // the destination of the stream. + this._controller.watchEvent( + "STREAM", + event => + event.StreamStatus === "SUCCEEDED" && "SOCKS_USERNAME" in event, + async event => { + const circuitStatuses = await this._controller.getInfo( + "circuit-status" + ); + if (!circuitStatuses) { + return; + } + for (const status of circuitStatuses) { + if (status.id === event.CircuitID && status.circuit.length) { + // The id in the circuit begins with a $ sign + const bridgeId = status.circuit[0][0].substr(1); + if (bridgeId !== this._currentBridge) { + this._currentBridge = bridgeId; + this._updateConnectedBridges(); + } + break; + } + } + } + ); + }); + } catch (err) { + console.warn( + "We could not load torbutton, bridge statuses will not be updated", + err + ); + } + + // Add a new bridge + prefpane.querySelector(selectors.bridges.addHeader).textContent = + TorStrings.settings.bridgeAdd; + prefpane + .querySelector(selectors.bridges.addBuiltinLabel) + .setAttribute("value", TorStrings.settings.bridgeSelectBrowserBuiltin); + { + let button = prefpane.querySelector(selectors.bridges.addBuiltinButton); + button.setAttribute("label", TorStrings.settings.bridgeSelectBuiltin); + button.addEventListener("command", e => { + this.onAddBuiltinBridge(); + }); + } + prefpane + .querySelector(selectors.bridges.requestLabel) + .setAttribute("value", TorStrings.settings.bridgeRequestFromTorProject); + { + let button = prefpane.querySelector(selectors.bridges.requestButton); + button.setAttribute("label", TorStrings.settings.bridgeRequest); + button.addEventListener("command", e => { + this.onRequestBridge(); + }); + } + prefpane + .querySelector(selectors.bridges.enterLabel) + .setAttribute("value", TorStrings.settings.bridgeEnterKnown); + { + const button = prefpane.querySelector(selectors.bridges.enterButton); + button.setAttribute("label", TorStrings.settings.bridgeAddManually); + button.addEventListener("command", e => { + this.onAddBridgeManually(); + }); + } + + Services.obs.addObserver(this, TorConnectTopics.StateChange); + + // Advanced setup + prefpane.querySelector(selectors.advanced.header).innerText = + TorStrings.settings.advancedHeading; + prefpane.querySelector(selectors.advanced.label).textContent = + TorStrings.settings.advancedLabel; + { + let settingsButton = prefpane.querySelector(selectors.advanced.button); + settingsButton.setAttribute( + "label", + TorStrings.settings.advancedButton + ); + settingsButton.addEventListener("command", () => { + this.onAdvancedSettings(); + }); + } + + // Tor logs + prefpane + .querySelector(selectors.advanced.torLogsLabel) + .setAttribute("value", TorStrings.settings.showTorDaemonLogs); + let torLogsButton = prefpane.querySelector( + selectors.advanced.torLogsButton + ); + torLogsButton.setAttribute("label", TorStrings.settings.showLogs); + torLogsButton.addEventListener("command", () => { + this.onViewTorLogs(); + }); + }, + + init() { + this._populateXUL(); + + let onUnload = () => { + window.removeEventListener("unload", onUnload); + gConnectionPane.uninit(); + }; + window.addEventListener("unload", onUnload); + + window.addEventListener("resize", () => { + this._checkBridgeCardsHeight(); + }); + }, + + uninit() { + // unregister our observer topics + Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged); + Services.obs.removeObserver(this, TorConnectTopics.StateChange); + + if (this._controller !== null) { + this._controller.close(); + this._controller = null; + } + }, + + // whether the page should be present in about:preferences + get enabled() { + return TorProtocolService.ownsTorDaemon; + }, + + // + // Callbacks + // + + observe(subject, topic, data) { + switch (topic) { + // triggered when a TorSettings param has changed + case TorSettingsTopics.SettingChanged: { + let obj = subject?.wrappedJSObject; + switch (data) { + case TorSettingsData.QuickStartEnabled: { + this._enableQuickstartCheckbox.checked = obj.value; + break; + } + } + break; + } + // triggered when tor connect state changes and we may + // need to update the messagebox + case TorConnectTopics.StateChange: { + this.onStateChange(); + break; + } + } + }, + + async onInternetTest() { + const mrpc = new MoatRPC(); + let status = null; + try { + await mrpc.init(); + status = await mrpc.testInternetConnection(); + } catch (err) { + console.log("Error while checking the Internet connection", err); + } finally { + mrpc.uninit(); + } + if (status) { + this._internetStatus = status.successful + ? InternetStatus.Online + : InternetStatus.Offline; + this._populateStatus(); + } + }, + + onStateChange() { + this._populateMessagebox(); + this._populateStatus(); + this._showAutoconfiguration(); + this._populateBridgeCards(); + }, + + onShowQr(bridgeString) { + const dialog = new BridgeQrDialog(); + dialog.openDialog(gSubDialog, bridgeString); + }, + + onCopyBridgeAddress(addressElem) { + let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService( + Ci.nsIClipboardHelper + ); + clipboard.copyString(addressElem.value); + }, + + onRemoveAllBridges() { + TorSettings.bridges.enabled = false; + TorSettings.bridges.bridge_strings = ""; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }, + + onAddBuiltinBridge() { + let builtinBridgeDialog = new BuiltinBridgeDialog(); + + let sizeObserver = null; + { + let ds = document.querySelector("#dialogStack"); + let boxObserver; + boxObserver = new MutationObserver(() => { + let dialogBox = document.querySelector(".dialogBox"); + if (dialogBox) { + sizeObserver = new MutationObserver(mutations => { + for (const m of mutations) { + if (m.attributeName === "style") { + builtinBridgeDialog.resized(); + break; + } + } + }); + sizeObserver.observe(dialogBox, { attributes: true }); + boxObserver.disconnect(); + } + }); + boxObserver.observe(ds, { childList: true, subtree: true }); + } + + builtinBridgeDialog.openDialog(gSubDialog, aBridgeType => { + sizeObserver.disconnect(); + + if (!aBridgeType) { + TorSettings.bridges.enabled = false; + TorSettings.bridges.builtin_type = ""; + } else { + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BuiltIn; + TorSettings.bridges.builtin_type = aBridgeType; + } + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + }); + }, + + // called when the request bridge button is activated + onRequestBridge() { + let requestBridgeDialog = new RequestBridgeDialog(); + requestBridgeDialog.openDialog(gSubDialog, aBridges => { + if (aBridges.length) { + let bridgeStrings = aBridges.join("\n"); + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.BridgeDB; + TorSettings.bridges.bridge_strings = bridgeStrings; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + } else { + TorSettings.bridges.enabled = false; + } + }); + }, + + onAddBridgeManually() { + let provideBridgeDialog = new ProvideBridgeDialog(); + provideBridgeDialog.openDialog(gSubDialog, aBridgeString => { + if (aBridgeString.length) { + TorSettings.bridges.enabled = true; + TorSettings.bridges.source = TorBridgeSource.UserProvided; + TorSettings.bridges.bridge_strings = aBridgeString; + TorSettings.saveToPrefs(); + TorSettings.applySettings().then(result => { + this._populateBridgeCards(); + }); + } else { + TorSettings.bridges.enabled = false; + TorSettings.bridges.source = TorBridgeSource.Invalid; + } + }); + }, + + onAdvancedSettings() { + let connectionSettingsDialog = new ConnectionSettingsDialog(); + connectionSettingsDialog.openDialog(gSubDialog); + }, + + onViewTorLogs() { + let torLogDialog = new TorLogDialog(); + torLogDialog.openDialog(gSubDialog); + }, + }; + return retval; +})(); /* gConnectionPane */ + +function makeBridgeId(bridgeString) { + // JS uses UTF-16. While most of these emojis are surrogate pairs, a few + // ones fit one UTF-16 character. So we could not use neither indices, + // nor substr, nor some function to split the string. + const emojis = [ + "😄️", + "😒️", + "😉", + "😭️", + "😂️", + "😎️", + "🤩️", + "😘", + "😜️", + "😏️", + "😷", + "🤢", + "🤕", + "🤧", + "🥵", + "🥶", + "🥴", + "😵️", + "🤮️", + "🤑", + "🤔", + "🫢", + "🤐", + "😮💨", + "😐", + "🤤", + "😴", + "🤯", + "🤠", + "🥳", + "🥸", + "🤓", + "🧐", + "😨", + "😳", + "🥺", + "🤬", + "😈", + "👿", + "💀", + "💩", + "🤡", + "👺", + "👻", + "👽", + "🦴", + "🤖", + "😸", + "🙈", + "🙉", + "🙊", + "💋", + "💖", + "💯", + "💢", + "💧", + "💨", + "💭", + "💤", + "👋", + "👌", + "✌", + "👍", + "👎", + "🤛", + "🙌", + "💪", + "🙏", + "✍", + "🧠", + "👀", + "👂", + "👅", + "🦷", + "🐾", + "🐶", + "🦊", + "🦝", + "🐈", + "🦁", + "🐯", + "🐴", + "🦄", + "🦓", + "🐮", + "🐷", + "🐑", + "🐪", + "🐘", + "🐭", + "🐰", + "🦔", + "🦇", + "🐻", + "🐨", + "🐼", + "🐔", + "🦨", + "🦘", + "🐦", + "🐧", + "🦩", + "🦉", + "🦜", + "🪶", + "🐸", + "🐊", + "🐢", + "🦎", + "🐍", + "🦖", + "🦀", + "🐬", + "🐙", + "🐌", + "🐝", + "🐞", + "🌸", + "🌲", + "🌵", + "🍀", + "🍁", + "🍇", + "🍉", + "🍊", + "🍋", + "🍌", + "🍍", + "🍎", + "🥥", + "🍐", + "🍒", + "🍓", + "🫐", + "🥝", + "🥔", + "🥕", + "🧅", + "🌰", + "🍄", + "🍞", + "🥞", + "🧀", + "🍖", + "🍔", + "🍟", + "🍕", + "🥚", + "🍿", + "🧂", + "🍙", + "🍦", + "🍩", + "🍪", + "🎂", + "🍬", + "🍭", + "🥛", + "☕", + "🫖", + "🍾", + "🍷", + "🍹", + "🍺", + "🍴", + "🥄", + "🫙", + "🧭", + "🌋", + "🪵", + "🏡", + "🏢", + "🏰", + "⛲", + "⛺", + "🎡", + "🚂", + "🚘", + "🚜", + "🚲", + "🚔", + "🚨", + "⛽", + "🚥", + "🚧", + "⚓", + "⛵", + "🛟", + "🪂", + "🚀", + "⌛", + "⏰", + "🌂", + "🌞", + "🌙", + "🌟", + "⛅", + "⚡", + "🔥", + "🌊", + "🎃", + "🎈", + "🎉", + "✨", + "🎀", + "🎁", + "🏆", + "🏅", + "🔮", + "🪄", + "🎾", + "🎳", + "🎲", + "🎭", + "🎨", + "🧵", + "🎩", + "📢", + "🔔", + "🎵", + "🎤", + "🎧", + "🎷", + "🎸", + "🥁", + "🔋", + "🔌", + "💻", + "💾", + "💿", + "🎬", + "📺", + "📷", + "🎮", + "🧩", + "🔍", + "💡", + "📖", + "💰", + "💼", + "📈", + "📌", + "📎", + "🔒", + "🔑", + "🔧", + "🪛", + "🔩", + "🧲", + "🔬", + "🔭", + "📡", + "🚪", + "🪑", + "⛔", + "🚩", + ]; + + // FNV-1a implementation that is compatible with other languages + const prime = 0x01000193; + const offset = 0x811c9dc5; + let hash = offset; + const encoder = new TextEncoder(); + for (const charCode of encoder.encode(bridgeString)) { + hash = Math.imul(hash ^ charCode, prime); + } + + const hashBytes = [ + ((hash & 0x7f000000) >> 24) | (hash < 0 ? 0x80 : 0), + (hash & 0x00ff0000) >> 16, + (hash & 0x0000ff00) >> 8, + hash & 0x000000ff, + ]; + return hashBytes.map(b => emojis[b]); +} + +function parseBridgeLine(line) { + const re = /^([^\s]+\s+)?([0-9a-fA-F.[]:]+:[0-9]{1,5})\s*([0-9a-fA-F]{40})(\s+.+)?/; + const matches = line.match(re); + if (!matches) { + return null; + } + let bridge = { addr: matches[2] }; + if (matches[1] !== undefined) { + bridge.transport = matches[1].trim(); + } + if (matches[3] !== undefined) { + bridge.id = matches[3].toUpperCase(); + } + if (matches[4] !== undefined) { + bridge.args = matches[4].trim(); + } + return bridge; +} diff --git a/browser/components/torpreferences/content/connectionPane.xhtml b/browser/components/torpreferences/content/connectionPane.xhtml new file mode 100644 index 0000000000000..67f98685d8038 --- /dev/null +++ b/browser/components/torpreferences/content/connectionPane.xhtml @@ -0,0 +1,177 @@ +<!-- Tor panel --> + +<script type="application/javascript" + src="chrome://browser/content/torpreferences/connectionPane.js"/> +<html:template id="template-paneConnection"> + +<!-- Tor Connect Message Box --> +<groupbox data-category="paneConnection" hidden="true"> + <html:div id="torPreferences-connectMessageBox" + class="subcategory" + data-category="paneConnection" + hidden="true"> + html:table + html:tr + html:td + <html:div id="torPreferences-connectMessageBox-icon"/> + </html:td> + <html:td id="torPreferences-connectMessageBox-message"> + </html:td> + html:td + <html:button id="torPreferences-connectMessageBox-button"> + </html:button> + </html:td> + </html:tr> + </html:table> + </html:div> +</groupbox> + +<hbox id="torPreferencesCategory" + class="subcategory" + data-category="paneConnection" + hidden="true"> + <html:h1 id="torPreferences-header"/> +</hbox> + +<groupbox data-category="paneConnection" + hidden="true"> + <description flex="1"> + <html:span id="torPreferences-description" class="tail-with-learn-more"/> + <label id="torPreferences-learnMore" class="learnMore text-link" is="text-link"/> + </description> +</groupbox> + +<groupbox id="torPreferences-status-group" + data-category="paneConnection"> + <hbox id="torPreferences-status-box"> + <image id="torPreferences-status-internet-icon"/> + <html:span id="torPreferences-status-internet-label"/> + <button id="torPreferences-status-internet-test"/> + <image id="torPreferences-status-internet-statusIcon"/> + <html:span id="torPreferences-status-internet-status"/> + <image id="torPreferences-status-tor-icon"/> + <html:span id="torPreferences-status-tor-label"/> + <image id="torPreferences-status-tor-statusIcon"/> + <html:span id="torPreferences-status-tor-status"/> + </hbox> +</groupbox> + +<!-- Quickstart --> +<groupbox id="torPreferences-quickstart-group" + data-category="paneConnection" + hidden="true"> + <html:h2 id="torPreferences-quickstart-header"/> + <description flex="1"> + <html:span id="torPreferences-quickstart-description"/> + </description> + <checkbox id="torPreferences-quickstart-toggle"/> +</groupbox> + +<!-- Bridges --> +<hbox class="subcategory" + data-category="paneConnection" + hidden="true"> + <html:h1 id="torPreferences-bridges-header"/> +</hbox> +<groupbox id="torPreferences-bridges-group" + data-category="paneConnection" + hidden="true"> + <description flex="1"> + <html:span id="torPreferences-bridges-description" class="tail-with-learn-more"/> + <label id="torPreferences-bridges-learnMore" class="learnMore text-link" is="text-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="torMarginFix primary"/> + </hbox> + <html:h2 id="torPreferences-currentBridges-header"> + <html:span id="torPreferences-currentBridges-headerText"/> + <html:input type="checkbox" id="torPreferences-currentBridges-switch" class="toggle-button"/> + </html:h2> + <menupopup id="torPreferences-bridgeCard-menu"/> + <vbox id="torPreferences-bridgeCard-template" class="torPreferences-bridgeCard"> + <hbox class="torPreferences-bridgeCard-heading"> + <html:div class="torPreferences-bridgeCard-id"/> + <html:div class="torPreferences-bridgeCard-headingAddr"/> + <html:div class="torPreferences-bridgeCard-buttons"> + <html:span class="torPreferences-bridgeCard-connectedBadge"> + <image class="torPreferences-bridgeCard-connectedIcon"/> + <html:span class="torPreferences-bridgeCard-connectedLabel"/> + </html:span> + <html:button class="torPreferences-bridgeCard-options stop-click"/> + </html:div> + </hbox> + <box class="torPreferences-bridgeCard-grid"> + <box class="torPreferences-bridgeCard-qrWrapper"> + <box class="torPreferences-bridgeCard-qr stop-click"> + <html:div class="torPreferences-bridgeCard-qrCode"/> + <html:div class="torPreferences-bridgeCard-qrOnionBox"/> + <html:div class="torPreferences-bridgeCard-qrOnion"/> + </box> + <html:div class="torPreferences-bridgeCard-filler"/> + </box> + <description class="torPreferences-bridgeCard-share"></description> + <hbox class="torPreferences-bridgeCard-addrBox"> + <html:input class="torPreferences-bridgeCard-addr torMarginFix stop-click" type="text" readonly="readonly"/> + </hbox> + <hbox class="torPreferences-bridgeCard-learnMoreBox" align="center"> + <label class="torPreferences-bridgeCard-learnMore learnMore text-link stop-click" is="text-link"/> + </hbox> + <hbox class="torPreferences-bridgeCard-copy" align="center"> + <button class="torPreferences-bridgeCard-copyButton stop-click"/> + </hbox> + </box> + </vbox> + <vbox id="torPreferences-currentBridges-cards"></vbox> + <vbox align="center"> + <button id="torPreferences-currentBridges-showAll"/> + <button id="torPreferences-currentBridges-removeAll" class="primary danger-button"/> + </vbox> + <html:h2 id="torPreferences-addBridge-header"></html:h2> + <hbox align="center"> + <label id="torPreferences-addBridge-labelBuiltinBridge"/> + <space flex="1"/> + <button id="torPreferences-addBridge-buttonBuiltinBridge" class="torMarginFix"/> + </hbox> + <hbox align="center"> + <label id="torPreferences-addBridge-labelRequestBridge"/> + <space flex="1"/> + <button id="torPreferences-addBridge-buttonRequestBridge" class="torMarginFix"/> + </hbox> + <hbox align="center"> + <label id="torPreferences-addBridge-labelEnterBridge"/> + <space flex="1"/> + <button id="torPreferences-addBridge-buttonEnterBridge" class="torMarginFix"/> + </hbox> +</groupbox> + +<!-- Advanced --> +<hbox class="subcategory" + data-category="paneConnection" + hidden="true"> + <html:h1 id="torPreferences-advanced-header"/> +</hbox> +<groupbox id="torPreferences-advanced-group" + data-category="paneConnection" + hidden="true"> + <box id="torPreferences-advanced-grid"> + <hbox id="torPreferences-advanced-hbox" align="center"> + <label id="torPreferences-advanced-label"/> + </hbox> + <hbox align="center"> + <button id="torPreferences-advanced-button"/> + </hbox> + <hbox id="torPreferences-torDaemon-hbox" align="center"> + <label id="torPreferences-torLogs"/> + </hbox> + <hbox align="center" data-subcategory="viewlogs"> + <button id="torPreferences-buttonTorLogs"/> + </hbox> + </box> +</groupbox> +</html:template> diff --git a/browser/components/torpreferences/content/connectionSettingsDialog.jsm b/browser/components/torpreferences/content/connectionSettingsDialog.jsm new file mode 100644 index 0000000000000..abc177c43f884 --- /dev/null +++ b/browser/components/torpreferences/content/connectionSettingsDialog.jsm @@ -0,0 +1,393 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["ConnectionSettingsDialog"]; + +const { TorSettings, TorProxyType } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +class ConnectionSettingsDialog { + constructor() { + this._dialog = null; + this._useProxyCheckbox = null; + this._proxyTypeLabel = null; + this._proxyTypeMenulist = null; + this._proxyAddressLabel = null; + this._proxyAddressTextbox = null; + this._proxyPortLabel = null; + this._proxyPortTextbox = null; + this._proxyUsernameLabel = null; + this._proxyUsernameTextbox = null; + this._proxyPasswordLabel = null; + this._proxyPasswordTextbox = null; + this._useFirewallCheckbox = null; + this._allowedPortsLabel = null; + this._allowedPortsTextbox = null; + } + + static get selectors() { + return { + header: "#torPreferences-connection-header", + useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy", + proxyTypeLabel: "label#torPreferences-localProxy-type", + proxyTypeList: "menulist#torPreferences-localProxy-builtinList", + proxyAddressLabel: "label#torPreferences-localProxy-address", + proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress", + proxyPortLabel: "label#torPreferences-localProxy-port", + proxyPortTextbox: "input#torPreferences-localProxy-textboxPort", + proxyUsernameLabel: "label#torPreferences-localProxy-username", + proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername", + proxyPasswordLabel: "label#torPreferences-localProxy-password", + proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword", + useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall", + firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts", + firewallAllowedPortsTextbox: + "input#torPreferences-connection-textboxAllowedPorts", + }; + } + + // disables the provided list of elements + _setElementsDisabled(elements, disabled) { + for (let currentElement of elements) { + currentElement.disabled = disabled; + } + } + + _populateXUL(window, aDialog) { + const selectors = ConnectionSettingsDialog.selectors; + + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + dialogWin.setAttribute( + "title", + TorStrings.settings.connectionSettingsDialogTitle + ); + this._dialog.querySelector(selectors.header).textContent = + TorStrings.settings.connectionSettingsDialogHeader; + + // Local Proxy + this._useProxyCheckbox = this._dialog.querySelector( + selectors.useProxyCheckbox + ); + this._useProxyCheckbox.setAttribute( + "label", + TorStrings.settings.useLocalProxy + ); + this._useProxyCheckbox.addEventListener("command", e => { + const checked = this._useProxyCheckbox.checked; + this.onToggleProxy(checked); + }); + this._proxyTypeLabel = this._dialog.querySelector(selectors.proxyTypeLabel); + this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType); + + let mockProxies = [ + { + value: TorProxyType.Socks4, + label: TorStrings.settings.proxyTypeSOCKS4, + }, + { + value: TorProxyType.Socks5, + label: TorStrings.settings.proxyTypeSOCKS5, + }, + { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP }, + ]; + this._proxyTypeMenulist = this._dialog.querySelector( + selectors.proxyTypeList + ); + this._proxyTypeMenulist.addEventListener("command", e => { + const value = this._proxyTypeMenulist.value; + this.onSelectProxyType(value); + }); + for (let currentProxy of mockProxies) { + let menuEntry = window.document.createXULElement("menuitem"); + menuEntry.setAttribute("value", currentProxy.value); + menuEntry.setAttribute("label", currentProxy.label); + this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry); + } + + this._proxyAddressLabel = this._dialog.querySelector( + selectors.proxyAddressLabel + ); + this._proxyAddressLabel.setAttribute( + "value", + TorStrings.settings.proxyAddress + ); + this._proxyAddressTextbox = this._dialog.querySelector( + selectors.proxyAddressTextbox + ); + this._proxyAddressTextbox.setAttribute( + "placeholder", + TorStrings.settings.proxyAddressPlaceholder + ); + this._proxyAddressTextbox.addEventListener("blur", e => { + let value = this._proxyAddressTextbox.value.trim(); + let colon = value.lastIndexOf(":"); + if (colon != -1) { + let maybePort = parseInt(value.substr(colon + 1)); + if (!isNaN(maybePort) && maybePort > 0 && maybePort < 65536) { + this._proxyAddressTextbox.value = value.substr(0, colon); + this._proxyPortTextbox.value = maybePort; + } + } + }); + this._proxyPortLabel = this._dialog.querySelector(selectors.proxyPortLabel); + this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort); + this._proxyPortTextbox = this._dialog.querySelector( + selectors.proxyPortTextbox + ); + this._proxyUsernameLabel = this._dialog.querySelector( + selectors.proxyUsernameLabel + ); + this._proxyUsernameLabel.setAttribute( + "value", + TorStrings.settings.proxyUsername + ); + this._proxyUsernameTextbox = this._dialog.querySelector( + selectors.proxyUsernameTextbox + ); + this._proxyUsernameTextbox.setAttribute( + "placeholder", + TorStrings.settings.proxyUsernamePasswordPlaceholder + ); + this._proxyPasswordLabel = this._dialog.querySelector( + selectors.proxyPasswordLabel + ); + this._proxyPasswordLabel.setAttribute( + "value", + TorStrings.settings.proxyPassword + ); + this._proxyPasswordTextbox = this._dialog.querySelector( + selectors.proxyPasswordTextbox + ); + this._proxyPasswordTextbox.setAttribute( + "placeholder", + TorStrings.settings.proxyUsernamePasswordPlaceholder + ); + + this.onToggleProxy(false); + if (TorSettings.proxy.enabled) { + this.onToggleProxy(true); + this.onSelectProxyType(TorSettings.proxy.type); + this._proxyAddressTextbox.value = TorSettings.proxy.address; + this._proxyPortTextbox.value = TorSettings.proxy.port; + this._proxyUsernameTextbox.value = TorSettings.proxy.username; + this._proxyPasswordTextbox.value = TorSettings.proxy.password; + } + + // Local firewall + this._useFirewallCheckbox = this._dialog.querySelector( + selectors.useFirewallCheckbox + ); + this._useFirewallCheckbox.setAttribute( + "label", + TorStrings.settings.useFirewall + ); + this._useFirewallCheckbox.addEventListener("command", e => { + const checked = this._useFirewallCheckbox.checked; + this.onToggleFirewall(checked); + }); + this._allowedPortsLabel = this._dialog.querySelector( + selectors.firewallAllowedPortsLabel + ); + this._allowedPortsLabel.setAttribute( + "value", + TorStrings.settings.allowedPorts + ); + this._allowedPortsTextbox = this._dialog.querySelector( + selectors.firewallAllowedPortsTextbox + ); + this._allowedPortsTextbox.setAttribute( + "placeholder", + TorStrings.settings.allowedPortsPlaceholder + ); + + this.onToggleFirewall(false); + if (TorSettings.firewall.enabled) { + this.onToggleFirewall(true); + this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join( + ", " + ); + } + + this._dialog.addEventListener("dialogaccept", e => { + this._applySettings(); + }); + } + + // callback when proxy is toggled + onToggleProxy(enabled) { + this._useProxyCheckbox.checked = enabled; + let disabled = !enabled; + + this._setElementsDisabled( + [ + this._proxyTypeLabel, + this._proxyTypeMenulist, + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + disabled + ); + if (enabled) { + this.onSelectProxyType(this._proxyTypeMenulist.value); + } + } + + // callback when proxy type is changed + onSelectProxyType(value) { + if (typeof value === "string") { + value = parseInt(value); + } + + this._proxyTypeMenulist.value = value; + switch (value) { + case TorProxyType.Invalid: { + this._setElementsDisabled( + [ + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + true + ); // DISABLE + + this._proxyAddressTextbox.value = ""; + this._proxyPortTextbox.value = ""; + this._proxyUsernameTextbox.value = ""; + this._proxyPasswordTextbox.value = ""; + break; + } + case TorProxyType.Socks4: { + this._setElementsDisabled( + [ + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + ], + false + ); // ENABLE + this._setElementsDisabled( + [ + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + true + ); // DISABLE + + this._proxyUsernameTextbox.value = ""; + this._proxyPasswordTextbox.value = ""; + break; + } + case TorProxyType.Socks5: + case TorProxyType.HTTPS: { + this._setElementsDisabled( + [ + this._proxyAddressLabel, + this._proxyAddressTextbox, + this._proxyPortLabel, + this._proxyPortTextbox, + this._proxyUsernameLabel, + this._proxyUsernameTextbox, + this._proxyPasswordLabel, + this._proxyPasswordTextbox, + ], + false + ); // ENABLE + break; + } + } + } + + // callback when firewall proxy is toggled + onToggleFirewall(enabled) { + this._useFirewallCheckbox.checked = enabled; + let disabled = !enabled; + + this._setElementsDisabled( + [this._allowedPortsLabel, this._allowedPortsTextbox], + disabled + ); + } + + // pushes settings from UI to tor + _applySettings() { + const type = this._useProxyCheckbox.checked + ? parseInt(this._proxyTypeMenulist.value) + : TorProxyType.Invalid; + const address = this._proxyAddressTextbox.value; + const port = this._proxyPortTextbox.value; + const username = this._proxyUsernameTextbox.value; + const password = this._proxyPasswordTextbox.value; + switch (type) { + case TorProxyType.Invalid: + TorSettings.proxy.enabled = false; + break; + case TorProxyType.Socks4: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + break; + case TorProxyType.Socks5: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + TorSettings.proxy.username = username; + TorSettings.proxy.password = password; + break; + case TorProxyType.HTTPS: + TorSettings.proxy.enabled = true; + TorSettings.proxy.type = type; + TorSettings.proxy.address = address; + TorSettings.proxy.port = port; + TorSettings.proxy.username = username; + TorSettings.proxy.password = password; + break; + } + + let portListString = this._useFirewallCheckbox.checked + ? this._allowedPortsTextbox.value + : ""; + if (portListString) { + TorSettings.firewall.enabled = true; + TorSettings.firewall.allowed_ports = portListString; + } else { + TorSettings.firewall.enabled = false; + } + + TorSettings.saveToPrefs(); + TorSettings.applySettings(); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(window, aDialog); + }, 0); + } + + openDialog(gSubDialog) { + gSubDialog.open( + "chrome://browser/content/torpreferences/connectionSettingsDialog.xhtml", + { features: "resizable=yes" }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/connectionSettingsDialog.xhtml b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml new file mode 100644 index 0000000000000..88aa979256f02 --- /dev/null +++ b/browser/components/torpreferences/content/connectionSettingsDialog.xhtml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-connection-dialog" + buttons="accept,cancel"> + <html:h3 id="torPreferences-connection-header">​</html:h3> + <box id="torPreferences-connection-grid"> + <!-- Local Proxy --> + <hbox class="torPreferences-connection-checkbox-container"> + <checkbox id="torPreferences-connection-toggleProxy" label="​"/> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-localProxy-type"/> + </hbox> + <hbox align="center"> + <spacer flex="1"/> + <menulist id="torPreferences-localProxy-builtinList" class="torMarginFix"> + <menupopup/> + </menulist> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-localProxy-address"/> + </hbox> + <hbox align="center"> + <html:input id="torPreferences-localProxy-textboxAddress" type="text" class="torMarginFix"/> + <label id="torPreferences-localProxy-port"/> + <!-- proxy-port-input class style pulled from preferences.css and used in the vanilla proxy setup menu --> + <html:input id="torPreferences-localProxy-textboxPort" class="proxy-port-input torMarginFix" hidespinbuttons="true" type="number" min="0" max="65535" maxlength="5"/> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-localProxy-username"/> + </hbox> + <hbox align="center"> + <html:input id="torPreferences-localProxy-textboxUsername" type="text" class="torMarginFix"/> + <label id="torPreferences-localProxy-password"/> + <html:input id="torPreferences-localProxy-textboxPassword" class="torMarginFix" type="password"/> + </hbox> + <!-- Firewall --> + <hbox class="torPreferences-connection-checkbox-container"> + <checkbox id="torPreferences-connection-toggleFirewall" label="​"/> + </hbox> + <hbox class="indent" align="center"> + <label id="torPreferences-connection-allowedPorts"/> + </hbox> + <hbox align="center"> + <html:input id="torPreferences-connection-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/> + </hbox> + </box> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let connectionSettingsDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-connection-dialog"); + connectionSettingsDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/network.svg b/browser/components/torpreferences/content/network.svg new file mode 100644 index 0000000000000..e1689b5e6d649 --- /dev/null +++ b/browser/components/torpreferences/content/network.svg @@ -0,0 +1,6 @@ +<!-- 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/. --> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity"> + <path d="M8.5 1a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm2.447 1.75a6.255 6.255 0 0 1 3.756 5.125l-2.229 0A9.426 9.426 0 0 0 10.54 2.75l.407 0zm-2.049 0a8.211 8.211 0 0 1 2.321 5.125l-5.438 0A8.211 8.211 0 0 1 8.102 2.75l.796 0zm-2.846 0 .408 0a9.434 9.434 0 0 0-1.934 5.125l-2.229 0A6.254 6.254 0 0 1 6.052 2.75zm0 11.5a6.252 6.252 0 0 1-3.755-5.125l2.229 0A9.426 9.426 0 0 0 6.46 14.25l-.408 0zm2.05 0a8.211 8.211 0 0 1-2.321-5.125l5.437 0a8.211 8.211 0 0 1-2.321 5.125l-.795 0zm2.846 0-.40 [...] +</svg> diff --git a/browser/components/torpreferences/content/provideBridgeDialog.jsm b/browser/components/torpreferences/content/provideBridgeDialog.jsm new file mode 100644 index 0000000000000..b73a6f533afa6 --- /dev/null +++ b/browser/components/torpreferences/content/provideBridgeDialog.jsm @@ -0,0 +1,69 @@ +"use strict"; + +var EXPORTED_SYMBOLS = ["ProvideBridgeDialog"]; + +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); + +const { TorSettings, TorBridgeSource } = ChromeUtils.import( + "resource:///modules/TorSettings.jsm" +); + +class ProvideBridgeDialog { + constructor() { + this._dialog = null; + this._textarea = null; + this._bridgeString = ""; + } + + static get selectors() { + return { + header: "#torPreferences-provideBridge-header", + textarea: "#torPreferences-provideBridge-textarea", + }; + } + + _populateXUL(aDialog) { + const selectors = ProvideBridgeDialog.selectors; + + this._dialog = aDialog; + const dialogWin = this._dialog.parentElement; + dialogWin.setAttribute("title", TorStrings.settings.provideBridgeTitle); + this._dialog.querySelector(selectors.header).textContent = + TorStrings.settings.provideBridgeHeader; + this._textarea = this._dialog.querySelector(selectors.textarea); + this._textarea.setAttribute( + "placeholder", + TorStrings.settings.provideBridgePlaceholder + ); + if ( + TorSettings.bridges.enabled && + TorSettings.bridges.source == TorBridgeSource.UserProvided + ) { + this._textarea.value = TorSettings.bridges.bridge_strings.join("\n"); + } + + this._dialog.addEventListener("dialogaccept", e => { + this._bridgeString = this._textarea.value; + }); + } + + init(window, aDialog) { + // defer to later until firefox has populated the dialog with all our elements + window.setTimeout(() => { + this._populateXUL(aDialog); + }, 0); + } + + openDialog(gSubDialog, aCloseCallback) { + gSubDialog.open( + "chrome://browser/content/torpreferences/provideBridgeDialog.xhtml", + { + features: "resizable=yes", + closingCallback: () => { + aCloseCallback(this._bridgeString); + }, + }, + this + ); + } +} diff --git a/browser/components/torpreferences/content/provideBridgeDialog.xhtml b/browser/components/torpreferences/content/provideBridgeDialog.xhtml new file mode 100644 index 0000000000000..28d19cadaf9c9 --- /dev/null +++ b/browser/components/torpreferences/content/provideBridgeDialog.xhtml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?> +<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?> + +<window type="child" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml%22%3E +<dialog id="torPreferences-provideBridge-dialog" + buttons="help,accept,cancel"> + <html:h3 id="torPreferences-provideBridge-header">​</html:h3> + <html:textarea id="torPreferences-provideBridge-textarea" multiline="true" rows="3"/> + <script type="application/javascript"><![CDATA[ + "use strict"; + + let provideBridgeDialog = window.arguments[0]; + let dialog = document.getElementById("torPreferences-provideBridge-dialog"); + provideBridgeDialog.init(window, dialog); + ]]></script> +</dialog> +</window> diff --git a/browser/components/torpreferences/content/requestBridgeDialog.jsm b/browser/components/torpreferences/content/requestBridgeDialog.jsm index 44ae11762def8..f14bbdcbbb448 100644 --- a/browser/components/torpreferences/content/requestBridgeDialog.jsm +++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm @@ -9,7 +9,7 @@ class RequestBridgeDialog { constructor() { this._dialog = null; this._submitButton = null; - this._dialogDescription = null; + this._dialogHeader = null; this._captchaImage = null; this._captchaEntryTextbox = null; this._captchaRefreshButton = null; @@ -22,7 +22,7 @@ class RequestBridgeDialog { return { submitButton: "accept" /* not really a selector but a key for dialog's getButton */, - dialogDescription: "description#torPreferences-requestBridge-description", + dialogHeader: "h3#torPreferences-requestBridge-header", captchaImage: "image#torPreferences-requestBridge-captchaImage", captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox", refreshCaptchaButton: @@ -34,7 +34,7 @@ class RequestBridgeDialog { }; }
- _populateXUL(dialog) { + _populateXUL(window, dialog) { const selectors = RequestBridgeDialog.selectors;
this._dialog = dialog; @@ -66,12 +66,15 @@ class RequestBridgeDialog { e.preventDefault(); this.onSubmitCaptcha(); }); + this._dialog.addEventListener("dialoghelp", e => { + window.top.openTrustedLinkIn( + "https://tb-manual.torproject.org/bridges/", + "tab" + ); + });
- this._dialogDescription = this._dialog.querySelector( - selectors.dialogDescription - ); - this._dialogDescription.textContent = - TorStrings.settings.contactingBridgeDB; + this._dialogHeader = this._dialog.querySelector(selectors.dialogHeader); + this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB;
this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
@@ -115,7 +118,7 @@ class RequestBridgeDialog { _setcaptchaImage(uri) { if (uri != this._captchaImage.src) { this._captchaImage.src = uri; - this._dialogDescription.textContent = TorStrings.settings.solveTheCaptcha; + this._dialogHeader.textContent = TorStrings.settings.solveTheCaptcha; this._setUIDisabled(false); this._captchaEntryTextbox.focus(); this._captchaEntryTextbox.select(); @@ -135,7 +138,7 @@ class RequestBridgeDialog { init(window, dialog) { // defer to later until firefox has populated the dialog with all our elements window.setTimeout(() => { - this._populateXUL(dialog); + this._populateXUL(window, dialog); }, 0); }
@@ -176,15 +179,14 @@ class RequestBridgeDialog { this._bridges = []; this._setUIDisabled(false); this._incorrectCaptchaHbox.style.visibility = "visible"; - console.log(eError); + console.log(aError); }); }
onRefreshCaptcha() { this._setUIDisabled(true); this._captchaImage.src = ""; - this._dialogDescription.textContent = - TorStrings.settings.contactingBridgeDB; + this._dialogHeader.textContent = TorStrings.settings.contactingBridgeDB; this._captchaEntryTextbox.value = ""; this._incorrectCaptchaHbox.style.visibility = "hidden";
@@ -201,9 +203,9 @@ class RequestBridgeDialog { closingCallback: () => { this.close(); aCloseCallback(this._bridges); - } + }, }, - this, + this ); } } diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml index 64c4507807fbf..b7286528a8a5a 100644 --- a/browser/components/torpreferences/content/requestBridgeDialog.xhtml +++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml @@ -7,11 +7,11 @@ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml%22%3E <dialog id="torPreferences-requestBridge-dialog" - buttons="accept,cancel"> + buttons="help,accept,cancel"> <!-- ok, so ​ is a zero-width space. We need to have *something* in the innerText so that XUL knows how tall the - description node is so that it can determine how large to make the dialog element's inner draw area. If we have - nothing in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( --> - <description id="torPreferences-requestBridge-description">​</description> + title node is so that it can determine how large to make the dialog element's inner draw area. If we have nothing + in the innerText, then it collapse to 0 height, and the contents of the dialog ends up partially hidden >:( --> + <html:h3 id="torPreferences-requestBridge-header">​</html:h3> <!-- init to transparent 400x125 png --> <image id="torPreferences-requestBridge-captchaImage" flex="1"/> <hbox id="torPreferences-requestBridge-inputHbox"> @@ -32,4 +32,4 @@ requestBridgeDialog.init(window, dialog); ]]></script> </dialog> -</window> \ No newline at end of file +</window> diff --git a/browser/components/torpreferences/content/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm index ecc684d878c20..94a57b9b165ee 100644 --- a/browser/components/torpreferences/content/torLogDialog.jsm +++ b/browser/components/torpreferences/content/torLogDialog.jsm @@ -2,6 +2,10 @@
var EXPORTED_SYMBOLS = ["TorLogDialog"];
+const { setTimeout, clearTimeout } = ChromeUtils.import( + "resource://gre/modules/Timer.jsm" +); + const { TorProtocolService } = ChromeUtils.import( "resource:///modules/TorProtocolService.jsm" ); @@ -12,6 +16,7 @@ class TorLogDialog { this._dialog = null; this._logTextarea = null; this._copyLogButton = null; + this._restoreButtonTimeout = null; }
static get selectors() { @@ -36,6 +41,19 @@ class TorLogDialog { this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog); this._copyLogButton.addEventListener("command", () => { this.copyTorLog(); + const label = this._copyLogButton.querySelector("label"); + label.setAttribute("value", TorStrings.settings.copied); + this._copyLogButton.classList.add("primary"); + + const RESTORE_TIME = 1200; + if (this._restoreButtonTimeout !== null) { + clearTimeout(this._restoreButtonTimeout); + } + this._restoreButtonTimeout = setTimeout(() => { + label.setAttribute("value", TorStrings.settings.copyLog); + this._copyLogButton.classList.remove("primary"); + this._restoreButtonTimeout = null; + }, RESTORE_TIME); });
this._logTextarea.value = TorProtocolService.getLog(); diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js deleted file mode 100644 index 58eec7ff74aa6..0000000000000 --- a/browser/components/torpreferences/content/torPane.js +++ /dev/null @@ -1,940 +0,0 @@ -"use strict"; - -/* global Services */ - -const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource, TorBuiltinBridgeTypes, TorProxyType } = ChromeUtils.import( - "resource:///modules/TorSettings.jsm" -); - -const { TorProtocolService } = ChromeUtils.import( - "resource:///modules/TorProtocolService.jsm" -); - -const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import( - "resource:///modules/TorConnect.jsm" -); - -const { TorLogDialog } = ChromeUtils.import( - "chrome://browser/content/torpreferences/torLogDialog.jsm" -); - -const { RequestBridgeDialog } = ChromeUtils.import( - "chrome://browser/content/torpreferences/requestBridgeDialog.jsm" -); - -ChromeUtils.defineModuleGetter( - this, - "TorStrings", - "resource:///modules/TorStrings.jsm" -); - -/* - Tor Pane - - Code for populating the XUL in about:preferences#tor, handling input events, interfacing with tor-launcher -*/ -const gTorPane = (function() { - /* CSS selectors for all of the Tor Network DOM elements we need to access */ - const selectors = { - category: { - title: "label#torPreferences-labelCategory", - }, - messageBox: { - box: "div#torPreferences-connectMessageBox", - message: "td#torPreferences-connectMessageBox-message", - button: "button#torPreferences-connectMessageBox-button", - }, - torPreferences: { - header: "h1#torPreferences-header", - description: "span#torPreferences-description", - learnMore: "label#torPreferences-learnMore", - }, - quickstart: { - header: "h2#torPreferences-quickstart-header", - description: "span#torPreferences-quickstart-description", - enableQuickstartCheckbox: "checkbox#torPreferences-quickstart-toggle", - }, - bridges: { - header: "h2#torPreferences-bridges-header", - description: "span#torPreferences-bridges-description", - learnMore: "label#torPreferences-bridges-learnMore", - useBridgeCheckbox: "checkbox#torPreferences-bridges-toggle", - bridgeSelectionRadiogroup: - "radiogroup#torPreferences-bridges-bridgeSelection", - builtinBridgeOption: "radio#torPreferences-bridges-radioBuiltin", - builtinBridgeList: "menulist#torPreferences-bridges-builtinList", - requestBridgeOption: "radio#torPreferences-bridges-radioRequestBridge", - requestBridgeButton: "button#torPreferences-bridges-buttonRequestBridge", - requestBridgeTextarea: - "textarea#torPreferences-bridges-textareaRequestBridge", - provideBridgeOption: "radio#torPreferences-bridges-radioProvideBridge", - provideBridgeDescription: - "description#torPreferences-bridges-descriptionProvideBridge", - provideBridgeTextarea: - "textarea#torPreferences-bridges-textareaProvideBridge", - }, - advanced: { - header: "h2#torPreferences-advanced-header", - description: "span#torPreferences-advanced-description", - learnMore: "label#torPreferences-advanced-learnMore", - useProxyCheckbox: "checkbox#torPreferences-advanced-toggleProxy", - proxyTypeLabel: "label#torPreferences-localProxy-type", - proxyTypeList: "menulist#torPreferences-localProxy-builtinList", - proxyAddressLabel: "label#torPreferences-localProxy-address", - proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress", - proxyPortLabel: "label#torPreferences-localProxy-port", - proxyPortTextbox: "input#torPreferences-localProxy-textboxPort", - proxyUsernameLabel: "label#torPreferences-localProxy-username", - proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername", - proxyPasswordLabel: "label#torPreferences-localProxy-password", - proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword", - useFirewallCheckbox: "checkbox#torPreferences-advanced-toggleFirewall", - firewallAllowedPortsLabel: "label#torPreferences-advanced-allowedPorts", - firewallAllowedPortsTextbox: - "input#torPreferences-advanced-textboxAllowedPorts", - torLogsLabel: "label#torPreferences-torLogs", - torLogsButton: "button#torPreferences-buttonTorLogs", - }, - }; /* selectors */ - - let retval = { - // cached frequently accessed DOM elements - _messageBox: null, - _messageBoxMessage: null, - _messageBoxButton: null, - _enableQuickstartCheckbox: null, - _useBridgeCheckbox: null, - _bridgeSelectionRadiogroup: null, - _builtinBridgeOption: null, - _builtinBridgeMenulist: null, - _requestBridgeOption: null, - _requestBridgeButton: null, - _requestBridgeTextarea: null, - _provideBridgeOption: null, - _provideBridgeTextarea: null, - _useProxyCheckbox: null, - _proxyTypeLabel: null, - _proxyTypeMenulist: null, - _proxyAddressLabel: null, - _proxyAddressTextbox: null, - _proxyPortLabel: null, - _proxyPortTextbox: null, - _proxyUsernameLabel: null, - _proxyUsernameTextbox: null, - _proxyPasswordLabel: null, - _proxyPasswordTextbox: null, - _useFirewallCheckbox: null, - _allowedPortsLabel: null, - _allowedPortsTextbox: null, - - // tor network settings - _bridgeSettings: null, - _proxySettings: null, - _firewallSettings: null, - - // disables the provided list of elements - _setElementsDisabled(elements, disabled) { - for (let currentElement of elements) { - currentElement.disabled = disabled; - } - }, - - // populate xul with strings and cache the relevant elements - _populateXUL() { - // saves tor settings to disk when navigate away from about:preferences - window.addEventListener("blur", val => { - TorProtocolService.flushSettings(); - }); - - document - .querySelector(selectors.category.title) - .setAttribute("value", TorStrings.settings.categoryTitle); - - let prefpane = document.getElementById("mainPrefPane"); - - // 'Connect to Tor' Message Bar - - this._messageBox = prefpane.querySelector(selectors.messageBox.box); - this._messageBoxMessage = prefpane.querySelector(selectors.messageBox.message); - this._messageBoxButton = prefpane.querySelector(selectors.messageBox.button); - // wire up connect button - this._messageBoxButton.addEventListener("click", () => { - TorConnect.beginBootstrap(); - TorConnect.openTorConnect(); - }); - - this._populateMessagebox = () => { - if (TorConnect.shouldShowTorConnect && - TorConnect.state === TorConnectState.Configuring) { - // set messagebox style and text - if (TorProtocolService.torBootstrapErrorOccurred()) { - this._messageBox.parentNode.style.display = null; - this._messageBox.className = "error"; - this._messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage; - this._messageBoxButton.innerText = TorStrings.torConnect.tryAgain; - } else { - this._messageBox.parentNode.style.display = null; - this._messageBox.className = "warning"; - this._messageBoxMessage.innerText = TorStrings.torConnect.connectMessage; - this._messageBoxButton.innerText = TorStrings.torConnect.torConnectButton; - } - } else { - // we need to explicitly hide the groupbox, as switching between - // the tor pane and other panes will 'unhide' (via the 'hidden' - // attribute) the groupbox, offsetting all of the content down - // by the groupbox's margin (even if content is 0 height) - this._messageBox.parentNode.style.display = "none"; - this._messageBox.className = "hidden"; - this._messageBoxMessage.innerText = ""; - this._messageBoxButton.innerText = ""; - } - } - this._populateMessagebox(); - Services.obs.addObserver(this, TorConnectTopics.StateChange); - - // update the messagebox whenever we come back to the page - window.addEventListener("focus", val => { - this._populateMessagebox(); - }); - - // Heading - prefpane.querySelector(selectors.torPreferences.header).innerText = - TorStrings.settings.torPreferencesHeading; - prefpane.querySelector(selectors.torPreferences.description).textContent = - TorStrings.settings.torPreferencesDescription; - { - let learnMore = prefpane.querySelector( - selectors.torPreferences.learnMore - ); - learnMore.setAttribute("value", TorStrings.settings.learnMore); - learnMore.setAttribute( - "href", - TorStrings.settings.learnMoreTorBrowserURL - ); - } - - // Quickstart - prefpane.querySelector(selectors.quickstart.header).innerText = - TorStrings.settings.quickstartHeading; - prefpane.querySelector(selectors.quickstart.description).textContent = - TorStrings.settings.quickstartDescription; - - this._enableQuickstartCheckbox = prefpane.querySelector( - selectors.quickstart.enableQuickstartCheckbox - ); - this._enableQuickstartCheckbox.setAttribute( - "label", - TorStrings.settings.quickstartCheckbox - ); - this._enableQuickstartCheckbox.addEventListener("command", e => { - const checked = this._enableQuickstartCheckbox.checked; - TorSettings.quickstart.enabled = checked; - TorSettings.saveToPrefs().applySettings(); - }); - this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled; - Services.obs.addObserver(this, TorSettingsTopics.SettingChanged); - - // Bridge setup - prefpane.querySelector(selectors.bridges.header).innerText = - TorStrings.settings.bridgesHeading; - prefpane.querySelector(selectors.bridges.description).textContent = - TorStrings.settings.bridgesDescription; - { - let learnMore = prefpane.querySelector(selectors.bridges.learnMore); - learnMore.setAttribute("value", TorStrings.settings.learnMore); - learnMore.setAttribute("href", TorStrings.settings.learnMoreBridgesURL); - } - - this._useBridgeCheckbox = prefpane.querySelector( - selectors.bridges.useBridgeCheckbox - ); - this._useBridgeCheckbox.setAttribute( - "label", - TorStrings.settings.useBridge - ); - this._useBridgeCheckbox.addEventListener("command", e => { - const checked = this._useBridgeCheckbox.checked; - gTorPane.onToggleBridge(checked).onUpdateBridgeSettings(); - }); - this._bridgeSelectionRadiogroup = prefpane.querySelector( - selectors.bridges.bridgeSelectionRadiogroup - ); - this._bridgeSelectionRadiogroup.value = TorBridgeSource.BuiltIn; - this._bridgeSelectionRadiogroup.addEventListener("command", e => { - const value = this._bridgeSelectionRadiogroup.value; - gTorPane.onSelectBridgeOption(value).onUpdateBridgeSettings(); - }); - - // Builtin bridges - this._builtinBridgeOption = prefpane.querySelector( - selectors.bridges.builtinBridgeOption - ); - this._builtinBridgeOption.setAttribute( - "label", - TorStrings.settings.selectBridge - ); - this._builtinBridgeOption.setAttribute("value", TorBridgeSource.BuiltIn); - this._builtinBridgeMenulist = prefpane.querySelector( - selectors.bridges.builtinBridgeList - ); - this._builtinBridgeMenulist.addEventListener("command", e => { - gTorPane.onUpdateBridgeSettings(); - }); - - // Request bridge - this._requestBridgeOption = prefpane.querySelector( - selectors.bridges.requestBridgeOption - ); - this._requestBridgeOption.setAttribute( - "label", - TorStrings.settings.requestBridgeFromTorProject - ); - this._requestBridgeOption.setAttribute("value", TorBridgeSource.BridgeDB); - this._requestBridgeButton = prefpane.querySelector( - selectors.bridges.requestBridgeButton - ); - this._requestBridgeButton.setAttribute( - "label", - TorStrings.settings.requestNewBridge - ); - this._requestBridgeButton.addEventListener("command", () => - gTorPane.onRequestBridge() - ); - this._requestBridgeTextarea = prefpane.querySelector( - selectors.bridges.requestBridgeTextarea - ); - - // Provide a bridge - this._provideBridgeOption = prefpane.querySelector( - selectors.bridges.provideBridgeOption - ); - this._provideBridgeOption.setAttribute( - "label", - TorStrings.settings.provideBridge - ); - this._provideBridgeOption.setAttribute( - "value", - TorBridgeSource.UserProvided - ); - prefpane.querySelector( - selectors.bridges.provideBridgeDescription - ).textContent = TorStrings.settings.provideBridgeDirections; - this._provideBridgeTextarea = prefpane.querySelector( - selectors.bridges.provideBridgeTextarea - ); - this._provideBridgeTextarea.setAttribute( - "placeholder", - TorStrings.settings.provideBridgePlaceholder - ); - this._provideBridgeTextarea.addEventListener("blur", () => { - gTorPane.onUpdateBridgeSettings(); - }); - - // Advanced setup - prefpane.querySelector(selectors.advanced.header).innerText = - TorStrings.settings.advancedHeading; - prefpane.querySelector(selectors.advanced.description).textContent = - TorStrings.settings.advancedDescription; - { - let learnMore = prefpane.querySelector(selectors.advanced.learnMore); - learnMore.setAttribute("value", TorStrings.settings.learnMore); - learnMore.setAttribute( - "href", - TorStrings.settings.learnMoreNetworkSettingsURL - ); - } - - // Local Proxy - this._useProxyCheckbox = prefpane.querySelector( - selectors.advanced.useProxyCheckbox - ); - this._useProxyCheckbox.setAttribute( - "label", - TorStrings.settings.useLocalProxy - ); - this._useProxyCheckbox.addEventListener("command", e => { - const checked = this._useProxyCheckbox.checked; - gTorPane.onToggleProxy(checked).onUpdateProxySettings(); - }); - this._proxyTypeLabel = prefpane.querySelector( - selectors.advanced.proxyTypeLabel - ); - this._proxyTypeLabel.setAttribute("value", TorStrings.settings.proxyType); - - let mockProxies = [ - { - value: TorProxyType.Socks4, - label: TorStrings.settings.proxyTypeSOCKS4, - }, - { - value: TorProxyType.Socks5, - label: TorStrings.settings.proxyTypeSOCKS5, - }, - { value: TorProxyType.HTTPS, label: TorStrings.settings.proxyTypeHTTP }, - ]; - this._proxyTypeMenulist = prefpane.querySelector( - selectors.advanced.proxyTypeList - ); - this._proxyTypeMenulist.addEventListener("command", e => { - const value = this._proxyTypeMenulist.value; - gTorPane.onSelectProxyType(value).onUpdateProxySettings(); - }); - for (let currentProxy of mockProxies) { - let menuEntry = document.createXULElement("menuitem"); - menuEntry.setAttribute("value", currentProxy.value); - menuEntry.setAttribute("label", currentProxy.label); - this._proxyTypeMenulist - .querySelector("menupopup") - .appendChild(menuEntry); - } - - this._proxyAddressLabel = prefpane.querySelector( - selectors.advanced.proxyAddressLabel - ); - this._proxyAddressLabel.setAttribute( - "value", - TorStrings.settings.proxyAddress - ); - this._proxyAddressTextbox = prefpane.querySelector( - selectors.advanced.proxyAddressTextbox - ); - this._proxyAddressTextbox.setAttribute( - "placeholder", - TorStrings.settings.proxyAddressPlaceholder - ); - this._proxyAddressTextbox.addEventListener("blur", () => { - gTorPane.onUpdateProxySettings(); - }); - this._proxyPortLabel = prefpane.querySelector( - selectors.advanced.proxyPortLabel - ); - this._proxyPortLabel.setAttribute("value", TorStrings.settings.proxyPort); - this._proxyPortTextbox = prefpane.querySelector( - selectors.advanced.proxyPortTextbox - ); - this._proxyPortTextbox.addEventListener("blur", () => { - gTorPane.onUpdateProxySettings(); - }); - this._proxyUsernameLabel = prefpane.querySelector( - selectors.advanced.proxyUsernameLabel - ); - this._proxyUsernameLabel.setAttribute( - "value", - TorStrings.settings.proxyUsername - ); - this._proxyUsernameTextbox = prefpane.querySelector( - selectors.advanced.proxyUsernameTextbox - ); - this._proxyUsernameTextbox.setAttribute( - "placeholder", - TorStrings.settings.proxyUsernamePasswordPlaceholder - ); - this._proxyUsernameTextbox.addEventListener("blur", () => { - gTorPane.onUpdateProxySettings(); - }); - this._proxyPasswordLabel = prefpane.querySelector( - selectors.advanced.proxyPasswordLabel - ); - this._proxyPasswordLabel.setAttribute( - "value", - TorStrings.settings.proxyPassword - ); - this._proxyPasswordTextbox = prefpane.querySelector( - selectors.advanced.proxyPasswordTextbox - ); - this._proxyPasswordTextbox.setAttribute( - "placeholder", - TorStrings.settings.proxyUsernamePasswordPlaceholder - ); - this._proxyPasswordTextbox.addEventListener("blur", () => { - gTorPane.onUpdateProxySettings(); - }); - - // Local firewall - this._useFirewallCheckbox = prefpane.querySelector( - selectors.advanced.useFirewallCheckbox - ); - this._useFirewallCheckbox.setAttribute( - "label", - TorStrings.settings.useFirewall - ); - this._useFirewallCheckbox.addEventListener("command", e => { - const checked = this._useFirewallCheckbox.checked; - gTorPane.onToggleFirewall(checked).onUpdateFirewallSettings(); - }); - this._allowedPortsLabel = prefpane.querySelector( - selectors.advanced.firewallAllowedPortsLabel - ); - this._allowedPortsLabel.setAttribute( - "value", - TorStrings.settings.allowedPorts - ); - this._allowedPortsTextbox = prefpane.querySelector( - selectors.advanced.firewallAllowedPortsTextbox - ); - this._allowedPortsTextbox.setAttribute( - "placeholder", - TorStrings.settings.allowedPortsPlaceholder - ); - this._allowedPortsTextbox.addEventListener("blur", () => { - gTorPane.onUpdateFirewallSettings(); - }); - - // Tor logs - prefpane - .querySelector(selectors.advanced.torLogsLabel) - .setAttribute("value", TorStrings.settings.showTorDaemonLogs); - let torLogsButton = prefpane.querySelector( - selectors.advanced.torLogsButton - ); - torLogsButton.setAttribute("label", TorStrings.settings.showLogs); - torLogsButton.addEventListener("command", () => { - gTorPane.onViewTorLogs(); - }); - - // Disable all relevant elements by default - this._setElementsDisabled( - [ - this._builtinBridgeOption, - this._builtinBridgeMenulist, - this._requestBridgeOption, - this._requestBridgeButton, - this._requestBridgeTextarea, - this._provideBridgeOption, - this._provideBridgeTextarea, - this._proxyTypeLabel, - this._proxyTypeMenulist, - this._proxyAddressLabel, - this._proxyAddressTextbox, - this._proxyPortLabel, - this._proxyPortTextbox, - this._proxyUsernameLabel, - this._proxyUsernameTextbox, - this._proxyPasswordLabel, - this._proxyPasswordTextbox, - this._allowedPortsLabel, - this._allowedPortsTextbox, - ], - true - ); - - // init bridge UI - for (let currentBridge of TorBuiltinBridgeTypes) { - let menuEntry = document.createXULElement("menuitem"); - menuEntry.setAttribute("value", currentBridge); - menuEntry.setAttribute("label", currentBridge); - this._builtinBridgeMenulist - .querySelector("menupopup") - .appendChild(menuEntry); - } - - if (TorSettings.bridges.enabled) { - this.onSelectBridgeOption(TorSettings.bridges.source); - this.onToggleBridge( - TorSettings.bridges.source != TorBridgeSource.Invalid - ); - switch (TorSettings.bridges.source) { - case TorBridgeSource.Invalid: - break; - case TorBridgeSource.BuiltIn: - this._builtinBridgeMenulist.value = TorSettings.bridges.builtin_type; - break; - case TorBridgeSource.BridgeDB: - this._requestBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n"); - break; - case TorBridgeSource.UserProvided: - this._provideBridgeTextarea.value = TorSettings.bridges.bridge_strings.join("\n"); - break; - } - } - - // init proxy UI - if (TorSettings.proxy.enabled) { - this.onToggleProxy(true); - this.onSelectProxyType(TorSettings.proxy.type); - this._proxyAddressTextbox.value = TorSettings.proxy.address; - this._proxyPortTextbox.value = TorSettings.proxy.port; - this._proxyUsernameTextbox.value = TorSettings.proxy.username; - this._proxyPasswordTextbox.value = TorSettings.proxy.password; - } - - // init firewall - if (TorSettings.firewall.enabled) { - this.onToggleFirewall(true); - this._allowedPortsTextbox.value = TorSettings.firewall.allowed_ports.join(", "); - } - }, - - init() { - this._populateXUL(); - - let onUnload = () => { - window.removeEventListener("unload", onUnload); - gTorPane.uninit(); - }; - window.addEventListener("unload", onUnload); - }, - - uninit() { - // unregister our observer topics - Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged); - Services.obs.removeObserver(this, TorConnectTopics.StateChange); - }, - - // whether the page should be present in about:preferences - get enabled() { - return TorProtocolService.ownsTorDaemon; - }, - - // - // Callbacks - // - - observe(subject, topic, data) { - switch (topic) { - // triggered when a TorSettings param has changed - case TorSettingsTopics.SettingChanged: { - let obj = subject?.wrappedJSObject; - switch(data) { - case TorSettingsData.QuickStartEnabled: { - this._enableQuickstartCheckbox.checked = obj.value; - break; - } - } - break; - } - // triggered when tor connect state changes and we may - // need to update the messagebox - case TorConnectTopics.StateChange: { - this._populateMessagebox(); - break; - } - } - }, - - // callback when using bridges toggled - onToggleBridge(enabled) { - this._useBridgeCheckbox.checked = enabled; - let disabled = !enabled; - - // first disable all the bridge related elements - this._setElementsDisabled( - [ - this._builtinBridgeOption, - this._builtinBridgeMenulist, - this._requestBridgeOption, - this._requestBridgeButton, - this._requestBridgeTextarea, - this._provideBridgeOption, - this._provideBridgeTextarea, - ], - disabled - ); - - // and selectively re-enable based on the radiogroup's current value - if (enabled) { - this.onSelectBridgeOption(this._bridgeSelectionRadiogroup.value); - } else { - this.onSelectBridgeOption(TorBridgeSource.Invalid); - } - return this; - }, - - // callback when a bridge option is selected - onSelectBridgeOption(source) { - if (typeof source === "string") { - source = parseInt(source); - } - - // disable all of the bridge elements under radio buttons - this._setElementsDisabled( - [ - this._builtinBridgeMenulist, - this._requestBridgeButton, - this._requestBridgeTextarea, - this._provideBridgeTextarea, - ], - true - ); - - if (source != TorBridgeSource.Invalid) { - this._bridgeSelectionRadiogroup.value = source; - } - - switch (source) { - case TorBridgeSource.BuiltIn: { - this._setElementsDisabled([this._builtinBridgeMenulist], false); - break; - } - case TorBridgeSource.BridgeDB: { - this._setElementsDisabled( - [this._requestBridgeButton, this._requestBridgeTextarea], - false - ); - break; - } - case TorBridgeSource.UserProvided: { - this._setElementsDisabled([this._provideBridgeTextarea], false); - break; - } - } - return this; - }, - - // called when the request bridge button is activated - onRequestBridge() { - let requestBridgeDialog = new RequestBridgeDialog(); - requestBridgeDialog.openDialog( - gSubDialog, - aBridges => { - if (aBridges.length > 0) { - let bridgeStrings = aBridges.join("\n"); - TorSettings.bridges.enabled = true; - TorSettings.bridges.source = TorBridgeSource.BridgeDB; - TorSettings.bridges.bridge_strings = bridgeStrings; - TorSettings.saveToPrefs(); - TorSettings.applySettings().then((result) => { - this._requestBridgeTextarea.value = bridgeStrings; - }); - } - } - ); - return this; - }, - - // pushes bridge settings from UI to tor - onUpdateBridgeSettings() { - let source = this._useBridgeCheckbox.checked - ? parseInt(this._bridgeSelectionRadiogroup.value) - : TorBridgeSource.Invalid; - - switch (source) { - case TorBridgeSource.Invalid: { - TorSettings.bridges.enabled = false; - } - break; - case TorBridgeSource.BuiltIn: { - // if there is a built-in bridge already selected, use that - let bridgeType = this._builtinBridgeMenulist.value; - console.log(`bridge type: ${bridgeType}`); - if (bridgeType) { - TorSettings.bridges.enabled = true; - TorSettings.bridges.source = TorBridgeSource.BuiltIn; - TorSettings.bridges.builtin_type = bridgeType; - } else { - TorSettings.bridges.enabled = false; - } - break; - } - case TorBridgeSource.BridgeDB: { - // if there are bridgedb bridges saved in the text area, use them - let bridgeStrings = this._requestBridgeTextarea.value; - if (bridgeStrings) { - TorSettings.bridges.enabled = true; - TorSettings.bridges.source = TorBridgeSource.BridgeDB; - TorSettings.bridges.bridge_strings = bridgeStrings; - } else { - TorSettings.bridges.enabled = false; - } - break; - } - case TorBridgeSource.UserProvided: { - // if bridges already exist in the text area, use them - let bridgeStrings = this._provideBridgeTextarea.value; - if (bridgeStrings) { - TorSettings.bridges.enabled = true; - TorSettings.bridges.source = TorBridgeSource.UserProvided; - TorSettings.bridges.bridge_strings = bridgeStrings; - } else { - TorSettings.bridges.enabled = false; - } - break; - } - } - TorSettings.saveToPrefs(); - TorSettings.applySettings(); - - return this; - }, - - // callback when proxy is toggled - onToggleProxy(enabled) { - this._useProxyCheckbox.checked = enabled; - let disabled = !enabled; - - this._setElementsDisabled( - [ - this._proxyTypeLabel, - this._proxyTypeMenulist, - this._proxyAddressLabel, - this._proxyAddressTextbox, - this._proxyPortLabel, - this._proxyPortTextbox, - this._proxyUsernameLabel, - this._proxyUsernameTextbox, - this._proxyPasswordLabel, - this._proxyPasswordTextbox, - ], - disabled - ); - this.onSelectProxyType(this._proxyTypeMenulist.value); - return this; - }, - - // callback when proxy type is changed - onSelectProxyType(value) { - if (typeof value === "string") { - value = parseInt(value); - } - - this._proxyTypeMenulist.value = value; - switch (value) { - case TorProxyType.Invalid: { - this._setElementsDisabled( - [ - this._proxyAddressLabel, - this._proxyAddressTextbox, - this._proxyPortLabel, - this._proxyPortTextbox, - this._proxyUsernameLabel, - this._proxyUsernameTextbox, - this._proxyPasswordLabel, - this._proxyPasswordTextbox, - ], - true - ); // DISABLE - - this._proxyAddressTextbox.value = ""; - this._proxyPortTextbox.value = ""; - this._proxyUsernameTextbox.value = ""; - this._proxyPasswordTextbox.value = ""; - break; - } - case TorProxyType.Socks4: { - this._setElementsDisabled( - [ - this._proxyAddressLabel, - this._proxyAddressTextbox, - this._proxyPortLabel, - this._proxyPortTextbox, - ], - false - ); // ENABLE - this._setElementsDisabled( - [ - this._proxyUsernameLabel, - this._proxyUsernameTextbox, - this._proxyPasswordLabel, - this._proxyPasswordTextbox, - ], - true - ); // DISABLE - - this._proxyUsernameTextbox.value = ""; - this._proxyPasswordTextbox.value = ""; - break; - } - case TorProxyType.Socks5: - case TorProxyType.HTTPS: { - this._setElementsDisabled( - [ - this._proxyAddressLabel, - this._proxyAddressTextbox, - this._proxyPortLabel, - this._proxyPortTextbox, - this._proxyUsernameLabel, - this._proxyUsernameTextbox, - this._proxyPasswordLabel, - this._proxyPasswordTextbox, - ], - false - ); // ENABLE - break; - } - } - return this; - }, - - // pushes proxy settings from UI to tor - onUpdateProxySettings() { - const type = this._useProxyCheckbox.checked - ? parseInt(this._proxyTypeMenulist.value) - : TorProxyType.Invalid; - const address = this._proxyAddressTextbox.value; - const port = this._proxyPortTextbox.value; - const username = this._proxyUsernameTextbox.value; - const password = this._proxyPasswordTextbox.value; - - switch (type) { - case TorProxyType.Invalid: - TorSettings.proxy.enabled = false; - break; - case TorProxyType.Socks4: - TorSettings.proxy.enabled = true; - TorSettings.proxy.type = type; - TorSettings.proxy.address = address; - TorSettings.proxy.port = port; - - break; - case TorProxyType.Socks5: - TorSettings.proxy.enabled = true; - TorSettings.proxy.type = type; - TorSettings.proxy.address = address; - TorSettings.proxy.port = port; - TorSettings.proxy.username = username; - TorSettings.proxy.password = password; - break; - case TorProxyType.HTTPS: - TorSettings.proxy.enabled = true; - TorSettings.proxy.type = type; - TorSettings.proxy.address = address; - TorSettings.proxy.port = port; - TorSettings.proxy.username = username; - TorSettings.proxy.password = password; - break; - } - TorSettings.saveToPrefs(); - TorSettings.applySettings(); - - return this; - }, - - // callback when firewall proxy is toggled - onToggleFirewall(enabled) { - this._useFirewallCheckbox.checked = enabled; - let disabled = !enabled; - - this._setElementsDisabled( - [this._allowedPortsLabel, this._allowedPortsTextbox], - disabled - ); - - return this; - }, - - // pushes firewall settings from UI to tor - onUpdateFirewallSettings() { - - let portListString = this._useFirewallCheckbox.checked - ? this._allowedPortsTextbox.value - : ""; - - if (portListString) { - TorSettings.firewall.enabled = true; - TorSettings.firewall.allowed_ports = portListString; - } else { - TorSettings.firewall.enabled = false; - } - TorSettings.saveToPrefs(); - TorSettings.applySettings(); - - return this; - }, - - onViewTorLogs() { - let torLogDialog = new TorLogDialog(); - torLogDialog.openDialog(gSubDialog); - }, - }; - return retval; -})(); /* gTorPane */ diff --git a/browser/components/torpreferences/content/torPane.xhtml b/browser/components/torpreferences/content/torPane.xhtml deleted file mode 100644 index 7c8071f2cf106..0000000000000 --- a/browser/components/torpreferences/content/torPane.xhtml +++ /dev/null @@ -1,157 +0,0 @@ -<!-- Tor panel --> - -<script type="application/javascript" - src="chrome://browser/content/torpreferences/torPane.js"/> -<html:template id="template-paneTor"> - -<!-- Tor Connect Message Box --> -<groupbox data-category="paneTor" hidden="true"> - <html:div id="torPreferences-connectMessageBox" - class="subcategory" - data-category="paneTor" - hidden="true"> - <html:table > - html:tr - html:td - <html:div id="torPreferences-connectMessageBox-icon"/> - </html:td> - <html:td id="torPreferences-connectMessageBox-message"> - </html:td> - html:td - <html:button id="torPreferences-connectMessageBox-button"> - </html:button> - </html:td> - </html:tr> - </html:table> - </html:div> -</groupbox> - -<hbox id="torPreferencesCategory" - class="subcategory" - data-category="paneTor" - hidden="true"> - <html:h1 id="torPreferences-header"/> -</hbox> - -<groupbox data-category="paneTor" - hidden="true"> - <description flex="1"> - <html:span id="torPreferences-description" class="tail-with-learn-more"/> - <label id="torPreferences-learnMore" class="learnMore text-link" is="text-link"/> - </description> -</groupbox> - -<!-- Quickstart --> -<groupbox id="torPreferences-quickstart-group" - data-category="paneTor" - hidden="true"> - <html:h2 id="torPreferences-quickstart-header"/> - <description flex="1"> - <html:span id="torPreferences-quickstart-description"/> - </description> - <checkbox id="torPreferences-quickstart-toggle"/> -</groupbox> - -<!-- Bridges --> -<groupbox id="torPreferences-bridges-group" - data-category="paneTor" - hidden="true"> - <html:h2 id="torPreferences-bridges-header"/> - <description flex="1"> - <html:span id="torPreferences-bridges-description" class="tail-with-learn-more"/> - <label id="torPreferences-bridges-learnMore" class="learnMore text-link" is="text-link"/> - </description> - <checkbox id="torPreferences-bridges-toggle"/> - <radiogroup id="torPreferences-bridges-bridgeSelection"> - <hbox class="indent"> - <radio id="torPreferences-bridges-radioBuiltin"/> - <spacer flex="1"/> - <menulist id="torPreferences-bridges-builtinList" class="torMarginFix"> - <menupopup/> - </menulist> - </hbox> - <vbox class="indent"> - <hbox> - <radio id="torPreferences-bridges-radioRequestBridge"/> - <space flex="1"/> - <button id="torPreferences-bridges-buttonRequestBridge" class="torMarginFix"/> - </hbox> - <html:textarea - id="torPreferences-bridges-textareaRequestBridge" - class="indent torMarginFix" - multiline="true" - rows="3" - readonly="true"/> - </vbox> - <hbox class="indent" flex="1"> - <vbox flex="1"> - <radio id="torPreferences-bridges-radioProvideBridge"/> - <description id="torPreferences-bridges-descriptionProvideBridge" class="indent"/> - <html:textarea - id="torPreferences-bridges-textareaProvideBridge" - class="indent torMarginFix" - multiline="true" - rows="3"/> - </vbox> - </hbox> - </radiogroup> -</groupbox> - -<!-- Advanced --> -<groupbox id="torPreferences-advanced-group" - data-category="paneTor" - hidden="true"> - <html:h2 id="torPreferences-advanced-header"/> - <description flex="1"> - <html:span id="torPreferences-advanced-description" class="tail-with-learn-more"/> - <label id="torPreferences-advanced-learnMore" class="learnMore text-link" is="text-link" style="display:none"/> - </description> - <box id="torPreferences-advanced-grid"> - <!-- Local Proxy --> - <hbox class="torPreferences-advanced-checkbox-container"> - <checkbox id="torPreferences-advanced-toggleProxy"/> - </hbox> - <hbox class="indent" align="center"> - <label id="torPreferences-localProxy-type"/> - </hbox> - <hbox align="center"> - <spacer flex="1"/> - <menulist id="torPreferences-localProxy-builtinList" class="torMarginFix"> - <menupopup/> - </menulist> - </hbox> - <hbox class="indent" align="center"> - <label id="torPreferences-localProxy-address"/> - </hbox> - <hbox align="center"> - <html:input id="torPreferences-localProxy-textboxAddress" type="text" class="torMarginFix"/> - <label id="torPreferences-localProxy-port"/> - <!-- proxy-port-input class style pulled from preferences.css and used in the vanilla proxy setup menu --> - <html:input id="torPreferences-localProxy-textboxPort" class="proxy-port-input torMarginFix" hidespinbuttons="true" type="number" min="0" max="65535" maxlength="5"/> - </hbox> - <hbox class="indent" align="center"> - <label id="torPreferences-localProxy-username"/> - </hbox> - <hbox align="center"> - <html:input id="torPreferences-localProxy-textboxUsername" type="text" class="torMarginFix"/> - <label id="torPreferences-localProxy-password"/> - <html:input id="torPreferences-localProxy-textboxPassword" class="torMarginFix" type="password"/> - </hbox> - <!-- Firewall --> - <hbox class="torPreferences-advanced-checkbox-container"> - <checkbox id="torPreferences-advanced-toggleFirewall"/> - </hbox> - <hbox class="indent" align="center"> - <label id="torPreferences-advanced-allowedPorts"/> - </hbox> - <hbox align="center"> - <html:input id="torPreferences-advanced-textboxAllowedPorts" type="text" class="torMarginFix" value="80,443"/> - </hbox> - </box> - <hbox id="torPreferences-torDaemon-hbox" align="center"> - <label id="torPreferences-torLogs"/> - <spacer flex="1"/> - <button id="torPreferences-buttonTorLogs" class="torMarginFix"/> - </hbox> -</groupbox> -</html:template> \ No newline at end of file diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css index b6eb0a740e5e4..31b6e29d679f3 100644 --- a/browser/components/torpreferences/content/torPreferences.css +++ b/browser/components/torpreferences/content/torPreferences.css @@ -1,9 +1,14 @@ @import url("chrome://branding/content/tor-styles.css");
-#category-tor > .category-icon { +#category-connection > .category-icon { list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg"); }
+html:dir(rtl) input[type="checkbox"].toggle-button::before { + /* For some reason, the rule from toggle-button.css is not applied... */ + scale: -1; +} + /* Connect Message Box */
#torPreferences-connectMessageBox { @@ -64,8 +69,7 @@
#torPreferences-connectMessageBox-message { line-height: 16px; - font-size: 1.11em; - padding-left: 8px!important; + padding-inline-start: 8px; }
#torPreferences-connectMessageBox-button { @@ -112,36 +116,302 @@ background-color: var(--purple-90); }
-/* Advanced Settings */ +/* Status */ +#torPreferences-status-box { + display: flex; + align-items: center; +}
-#torPreferences-advanced-grid { +#torPreferences-status-internet-icon, #torPreferences-status-tor-icon { + width: 18px; + height: 18px; + margin-inline-end: 8px; +} + +#torPreferences-status-internet-label, #torPreferences-status-tor-label { + font-weight: bold; +} + +#torPreferences-status-internet-icon { + list-style-image: url("chrome://devtools/skin/images/globe.svg"); +} + +#torPreferences-status-internet-statusIcon.online, +#torPreferences-status-internet-statusIcon.offline, +#torPreferences-status-tor-statusIcon { + margin-inline-start: 12px; + margin-inline-end: 9px; +} + +#torPreferences-status-internet-statusIcon.online, #torPreferences-status-tor-statusIcon.connected { + list-style-image: url("chrome://devtools/skin/images/check.svg"); + -moz-context-properties: fill; + fill: var(--purple-60); +} + +#torPreferences-status-internet-status { + margin-inline-end: 32px; +} + +#torPreferences-status-tor-icon { + list-style-image: url("chrome://browser/skin/onion.svg"); +} + +#torPreferences-status-internet-icon, #torPreferences-status-tor-icon { + -moz-context-properties: fill; + fill: var(--in-content-text-color); +} + +#torPreferences-status-tor-statusIcon, #torPreferences-status-internet-statusIcon.offline { + list-style-image: url("chrome://browser/skin/warning.svg"); +} + +#torPreferences-status-tor-statusIcon.blocked { + -moz-context-properties: fill; + fill: var(--red-60); +} + +/* Bridge settings */ +#torPreferences-bridges-location { + width: 280px; +} + +/* Bridge cards */ +:root { + --bridgeCard-animation-time: 0.25s; +} + +#torPreferences-currentBridges-cards { + /* The padding is needed because the mask-image creates an unexpected result + otherwise... */ + padding: 24px 4px; +} + +#torPreferences-currentBridges-cards.list-collapsed { + mask-image: linear-gradient(rgb(0, 0, 0), rgba(0, 0, 0, 0.1)); +} + +#torPreferences-currentBridges-cards.disabled { + opacity: 0.4; +} + +.torPreferences-bridgeCard { + padding: 16px 12px; + /* define border-radius here because of the transition */ + border-radius: 4px; + transition: margin var(--bridgeCard-animation-time), box-shadow 150ms; +} + +.torPreferences-bridgeCard.expanded { + margin: 12px 0; + background: var(--in-content-box-background); + box-shadow: var(--card-shadow); +} + +.torPreferences-bridgeCard:hover { + background: var(--in-content-box-background); + box-shadow: var(--card-shadow-hover); + cursor: pointer; +} + +.torPreferences-bridgeCard-heading { + display: flex; + align-items: center; +} + +.torPreferences-bridgeCard-id { + font-weight: 700; +} + +.torPreferences-bridgeCard-id .emoji { + margin-inline-start: 4px; + padding: 4px; + font-size: 20px; + border-radius: 4px; + background: var(--in-content-button-background-hover); +} + +.torPreferences-bridgeCard-headingAddr { + /* flex extends the element when needed, but without setting a width (any) the + overflow + ellipses does not work. */ + width: 20px; + flex: 1; + margin: 0 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.expanded .torPreferences-bridgeCard-headingAddr { + display: none; +} + +.torPreferences-bridgeCard-buttons { + display: flex; + align-items: center; + margin-inline-start: auto; + align-self: center; +} + +.torPreferences-bridgeCard-connectedBadge { + display: none; + padding: 8px 12px; + border-radius: 16px; + background: rgba(128, 0, 215, 0.1); + color: var(--purple-60); +} + +.currently-connected .torPreferences-bridgeCard-connectedBadge { + display: flex; +} + +.torPreferences-bridgeCard-connectedIcon { + margin-inline-start: 1px; + margin-inline-end: 7px; + list-style-image: url("chrome://devtools/skin/images/check.svg"); + -moz-context-properties: fill; + fill: var(--purple-60); +} + +.torPreferences-bridgeCard-options { + width: 24px; + min-width: 0; + height: 24px; + min-height: 0; + margin-inline-start: 8px; + padding: 1px; + background-image: url("chrome://global/skin/icons/more.svg"); + background-repeat: no-repeat; + background-position: center center; + fill: currentColor; + -moz-context-properties: fill; +} + +.torPreferences-bridgeCard-qrWrapper { + grid-area: bridge-qr; + display: flex; + flex-direction: column; +} + +.torPreferences-bridgeCard-qr { + width: 126px; + position: relative; +} + +.torPreferences-bridgeCard-qrCode { + width: 112px; + height: 112px; + /* Define these colors, as they will be passed to the QR code library */ + background: var(--in-content-box-background); + color: var(--in-content-text-color); +} + +.torPreferences-bridgeCard-qrOnionBox { + width: 28px; + height: 28px; + position: absolute; + top: 42px; + inset-inline-start: 42px; + background: var(--in-content-box-background); +} + +.torPreferences-bridgeCard-qrOnion { + width: 16px; + height: 16px; + position: absolute; + top: 48px; + inset-inline-start: 48px; + + mask: url("chrome://browser/skin/onion.svg"); + mask-repeat: no-repeat; + mask-size: 16px; + background: var(--in-content-text-color); +} + +.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnionBox { + background: var(--in-content-text-color); +} + +.torPreferences-bridgeCard-qr:hover .torPreferences-bridgeCard-qrOnion { + mask: url("chrome://global/skin/icons/search-glass.svg"); + background: var(--in-content-box-background); +} + +.torPreferences-bridgeCard-filler { + flex: 1; +} + +.torPreferences-bridgeCard-grid { + height: 0; /* We will set it in JS when expanding it! */ display: grid; - grid-template-columns: auto 1fr; + grid-template-rows: auto 1fr; + grid-template-columns: auto 1fr auto; + grid-template-areas: + 'bridge-qr bridge-share bridge-share' + 'bridge-qr bridge-address bridge-address' + 'bridge-qr bridge-learn-more bridge-copy'; + padding-top: 12px; + visibility: hidden; }
-.torPreferences-advanced-checkbox-container { - grid-column: 1 / 3; +.expanded .torPreferences-bridgeCard-grid { + visibility: visible; }
-#torPreferences-localProxy-textboxAddress, -#torPreferences-localProxy-textboxUsername, -#torPreferences-localProxy-textboxPassword, -#torPreferences-advanced-textboxAllowedPorts { - -moz-box-flex: 1; +.torPreferences-bridgeCard-grid.to-animate { + transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time); + overflow: hidden; +} + +.torPreferences-bridgeCard-share { + grid-area: bridge-share; +} + +.torPreferences-bridgeCard-addrBox { + grid-area: bridge-address; + display: flex; + align-items: center; + justify-content: center; + margin: 8px 0; +} + +.torPreferences-bridgeCard-addr { + width: 100%; +} + +.torPreferences-bridgeCard-leranMoreBox { + grid-area: bridge-learn-more; +} + +.torPreferences-bridgeCard-copy { + grid-area: bridge-copy; +} + +#torPreferences-bridgeCard-template { + display: none; +} + +/* Advanced Settings */ +#torPreferences-advanced-grid { + display: grid; + grid-template-columns: 1fr auto; }
-hbox#torPreferences-torDaemon-hbox { - margin-top: 20px; +#torPreferences-advanced-group button { + min-width: 150px; }
-description#torPreferences-requestBridge-description { - /*margin-bottom: 1em;*/ - min-height: 2em; +#torPreferences-advanced-hbox, #torPreferences-torDaemon-hbox { + padding-inline-end: 15px; +} + +h3#torPreferences-requestBridge-header { + margin: 0; }
image#torPreferences-requestBridge-captchaImage { - margin: 1em; - min-height: 125px; + margin: 16px 0 8px 0; + min-height: 140px; }
button#torPreferences-requestBridge-refreshCaptchaButton { @@ -160,6 +430,61 @@ dialog#torPreferences-requestBridge-dialog > hbox { margin-right : 4px; }
+/* Show bridge QR dialog */ +#bridgeQr-container { + position: relative; + height: 300px; +} + +#bridgeQr-target { + position: absolute; + width: 300px; + height: 300px; + left: calc(50% - 150px); + background: var(--in-content-box-background); + color: var(--in-content-text-color); +} + +#bridgeQr-onionBox { + position: absolute; + width: 70px; + height: 70px; + top: 115px; + left: calc(50% - 35px); + background-color: var(--in-content-box-background); +} + +#bridgeQr-onion { + position: absolute; + width: 38px; + height: 38px; + top: 131px; + left: calc(50% - 19px); + mask: url("chrome://browser/skin/onion.svg"); + mask-repeat: no-repeat; + mask-size: 38px; + background: var(--in-content-text-color); +} + +/* Builtin bridge dialog */ +#torPreferences-builtinBridge-header { + margin: 8px 0 10px 0; +} + +#torPreferences-builtinBridge-description { + margin-bottom: 18px; +} + +#torPreferences-builtinBridge-typeSelection { + margin-bottom: 16px; + min-height: 14em; /* Hack: make room for at least 4 lines of content for 3 types + 2 for spacing */ +} + +#torPreferences-builtinBridge-typeSelection radio label { + font-weight: 700; +} + +/* Request bridge dialog */ /* This hbox is hidden by css here by default so that the xul dialog allocates enough screen space for the error message @@ -178,6 +503,33 @@ groupbox#torPreferences-bridges-group textarea { overflow: auto; }
+/* Provide bridge dialog */ +#torPreferences-provideBridge-header { + margin-top: 8px; +} + +/* Connection settings dialog */ +#torPreferences-connection-header { + margin: 4px 0 14px 0; +} + +#torPreferences-connection-grid { + display: grid; + grid-template-columns: auto 1fr; +} + +.torPreferences-connection-checkbox-container { + grid-column: 1 / 3; +} + +#torPreferences-localProxy-textboxAddress, +#torPreferences-localProxy-textboxUsername, +#torPreferences-localProxy-textboxPassword, +#torPreferences-connection-textboxAllowedPorts { + -moz-box-flex: 1; +} + +/* Tor logs dialog */ textarea#torPreferences-torDialog-textarea { -moz-box-flex: 1; font-family: monospace; @@ -186,4 +538,4 @@ textarea#torPreferences-torDialog-textarea { overflow: auto; /* 10 lines */ min-height: 20em; -} \ No newline at end of file +} diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn index 552c92b2feff6..ed3bb441084c9 100644 --- a/browser/components/torpreferences/jar.mn +++ b/browser/components/torpreferences/jar.mn @@ -1,10 +1,19 @@ browser.jar: + content/browser/torpreferences/bridgeQrDialog.xhtml (content/bridgeQrDialog.xhtml) + content/browser/torpreferences/bridgeQrDialog.jsm (content/bridgeQrDialog.jsm) + content/browser/torpreferences/builtinBridgeDialog.xhtml (content/builtinBridgeDialog.xhtml) + content/browser/torpreferences/builtinBridgeDialog.jsm (content/builtinBridgeDialog.jsm) + content/browser/torpreferences/connectionSettingsDialog.xhtml (content/connectionSettingsDialog.xhtml) + content/browser/torpreferences/connectionSettingsDialog.jsm (content/connectionSettingsDialog.jsm) + content/browser/torpreferences/network.svg (content/network.svg) + content/browser/torpreferences/provideBridgeDialog.xhtml (content/provideBridgeDialog.xhtml) + content/browser/torpreferences/provideBridgeDialog.jsm (content/provideBridgeDialog.jsm) content/browser/torpreferences/requestBridgeDialog.xhtml (content/requestBridgeDialog.xhtml) content/browser/torpreferences/requestBridgeDialog.jsm (content/requestBridgeDialog.jsm) - content/browser/torpreferences/torCategory.inc.xhtml (content/torCategory.inc.xhtml) + content/browser/torpreferences/connectionCategory.inc.xhtml (content/connectionCategory.inc.xhtml) content/browser/torpreferences/torLogDialog.jsm (content/torLogDialog.jsm) content/browser/torpreferences/torLogDialog.xhtml (content/torLogDialog.xhtml) - content/browser/torpreferences/torPane.js (content/torPane.js) - content/browser/torpreferences/torPane.xhtml (content/torPane.xhtml) + content/browser/torpreferences/connectionPane.js (content/connectionPane.js) + content/browser/torpreferences/connectionPane.xhtml (content/connectionPane.xhtml) content/browser/torpreferences/torPreferences.css (content/torPreferences.css) content/browser/torpreferences/torPreferencesIcon.svg (content/torPreferencesIcon.svg)
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
commit 76f70fba50817ec90a8e00725dad9405f07d4a1d Author: Richard Pospesel richard@torproject.org AuthorDate: Wed Jan 19 19:09:51 2022 +0100
squash! Bug 27476: Implement about:torconnect captive portal within Tor Browser
Bug 40773: Update the about:torconnect frontend page to match additional UI flows --- browser/components/torconnect/TorConnectParent.jsm | 59 ++- .../torconnect/content/aboutTorConnect.css | 189 ++++--- .../torconnect/content/aboutTorConnect.js | 570 +++++++++++++++++---- .../torconnect/content/aboutTorConnect.xhtml | 43 +- .../components/torconnect/content/arrow-right.svg | 4 + browser/components/torconnect/content/bridge.svg | 5 + .../torconnect/content/connection-failure.svg | 5 + .../torconnect/content/connection-location.svg | 5 + browser/components/torconnect/content/globe.svg | 4 + .../torconnect/content/onion-slash-fillable.svg | 5 + browser/components/torconnect/jar.mn | 6 + toolkit/modules/RemotePageAccessManager.jsm | 13 +- 12 files changed, 720 insertions(+), 188 deletions(-)
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm index 2fbc2a5c7c7c7..dd3d1b2410f95 100644 --- a/browser/components/torconnect/TorConnectParent.jsm +++ b/browser/components/torconnect/TorConnectParent.jsm @@ -4,7 +4,7 @@ var EXPORTED_SYMBOLS = ["TorConnectParent"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); -const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import( +const { TorConnect, TorConnectTopics, TorConnectState, TorCensorshipLevel } = ChromeUtils.import( "resource:///modules/TorConnect.jsm" ); const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import( @@ -24,13 +24,15 @@ class TorConnectParent extends JSWindowActorParent {
this.state = { State: TorConnect.state, + DetectedCensorshiplevel: TorConnect.detectedCensorshiplevel, StateChanged: false, ErrorMessage: TorConnect.errorMessage, ErrorDetails: TorConnect.errorDetails, BootstrapProgress: TorConnect.bootstrapProgress, BootstrapStatus: TorConnect.bootstrapStatus, - ShowCopyLog: TorConnect.logHasWarningOrError, + ShowViewLog: TorConnect.logHasWarningOrError, QuickStartEnabled: TorSettings.quickstart.enabled, + CountryCodes: TorConnect.countryCodes, };
// JSWindowActiveParent derived objects cannot observe directly, so create a member @@ -46,10 +48,11 @@ class TorConnectParent extends JSWindowActorParent { // update our state struct based on received torconnect topics and forward on // to aboutTorConnect.js self.state.StateChanged = false; - switch(aTopic) { + switch (aTopic) { case TorConnectTopics.StateChange: { self.state.State = obj.state; self.state.StateChanged = true; + // clear any previous error information if we are bootstrapping if (self.state.State === TorConnectState.Bootstrapping) { self.state.ErrorMessage = null; @@ -60,7 +63,7 @@ class TorConnectParent extends JSWindowActorParent { case TorConnectTopics.BootstrapProgress: { self.state.BootstrapProgress = obj.progress; self.state.BootstrapStatus = obj.status; - self.state.ShowCopyLog = obj.hasWarnings; + self.state.ShowViewLog = obj.hasWarnings; break; } case TorConnectTopics.BootstrapComplete: { @@ -70,14 +73,21 @@ class TorConnectParent extends JSWindowActorParent { case TorConnectTopics.BootstrapError: { self.state.ErrorMessage = obj.message; self.state.ErrorDetails = obj.details; - self.state.ShowCopyLog = true; + self.state.DetectedCensorshiplevel = obj.censorshipLevel; + + // With severe censorshp, we offer user list of countries to try + if (self.state.DetectedCensorshiplevel == TorCensorshipLevel.Severe) { + self.state.CountryCodes = TorConnect.countryCodes; + } + + self.state.ShowViewLog = true; break; } case TorConnectTopics.FatalError: { // TODO: handle break; } - case TorSettingsTopics.SettingChanged:{ + case TorSettingsTopics.SettingChanged: { if (aData === TorSettingsData.QuickStartEnabled) { self.state.QuickStartEnabled = obj.value; } else { @@ -100,7 +110,10 @@ class TorConnectParent extends JSWindowActorParent { const topic = TorConnectTopics[key]; Services.obs.addObserver(this.torConnectObserver, topic); } - Services.obs.addObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged); + Services.obs.addObserver( + this.torConnectObserver, + TorSettingsTopics.SettingChanged + ); }
willDestroy() { @@ -109,10 +122,13 @@ class TorConnectParent extends JSWindowActorParent { const topic = TorConnectTopics[key]; Services.obs.removeObserver(this.torConnectObserver, topic); } - Services.obs.removeObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged); + Services.obs.removeObserver( + this.torConnectObserver, + TorSettingsTopics.SettingChanged + ); }
- receiveMessage(message) { + async receiveMessage(message) { switch (message.name) { case "torconnect:set-quickstart": TorSettings.quickstart.enabled = message.data; @@ -121,14 +137,23 @@ class TorConnectParent extends JSWindowActorParent { case "torconnect:open-tor-preferences": TorConnect.openTorPreferences(); break; - case "torconnect:copy-tor-logs": - return TorConnect.copyTorLogs(); + case "torconnect:view-tor-logs": + TorConnect.viewTorLogs(); + break; case "torconnect:cancel-bootstrap": TorConnect.cancelBootstrap(); break; case "torconnect:begin-bootstrap": TorConnect.beginBootstrap(); break; + case "torconnect:begin-autobootstrap": + TorConnect.beginAutoBootstrap(message.data); + break; + case "torconnect:restart": + Services.startup.quit( + Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit + ); + break; case "torconnect:get-init-args": // called on AboutTorConnect.init(), pass down all state data it needs to init
@@ -136,11 +161,15 @@ class TorConnectParent extends JSWindowActorParent { // so we always get fresh UI this.state.StateChanged = true; return { - TorStrings: TorStrings, - TorConnectState: TorConnectState, - Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr", - State: this.state, + TorStrings, + TorConnectState, + TorCensorshipLevel, + Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr", + State: this.state, + CountryNames: TorConnect.countryNames, }; + case "torconnect:get-country-codes": + return TorConnect.getCountryCodes(); } return undefined; } diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css index 14a3df2a59be3..0a3dc9fbd75fd 100644 --- a/browser/components/torconnect/content/aboutTorConnect.css +++ b/browser/components/torconnect/content/aboutTorConnect.css @@ -24,22 +24,133 @@ } }
-#connectButton { +#breadcrumbs { + display: flex; + margin: 0 0 24px 0; + color: var(--grey-40); +} + +#breadcrumbs.hidden { + visibility: hidden; +} + +.breadcrumb-item, .breadcrumb-separator { + display: flex; + margin: 0; + margin-inline-start: 20px; +} + +.breadcrumb-item { + cursor: pointer; + color: var(--in-content-text-color); +} + +.breadcrumb-item:hover { + color: var(--blue-60); +} + +.breadcrumb-separator { + width: 15px; + list-style-image: url("chrome://browser/content/torconnect/arrow-right.svg"); +} + +.breadcrumb-separator:dir(rtl) { + scale: -1 1; +} + +.breadcrumb-icon { + display: inline list-item; + list-style-position: inside; + fill: currentColor; + -moz-context-properties: fill; +} + +.breadcrumb-label { + margin-top: -1px; +} + +#breadcrumbs .active { + color: var(--blue-60); +} + +#breadcrumbs .disabled, #breadcrumbs .disabled:hover { + color: var(--green-90-a40); + cursor: default; +} + +#breadcrumbs .error { + color: var(--red-60); +} + +#connection-assist { + margin-left: 0; +} + +#connection-assist-icon { + list-style-image: url("chrome://browser/content/torconnect/onion-slash-fillable.svg"); +} + +#location-settings-icon { + list-style-image: url("chrome://browser/content/torconnect/globe.svg"); +} + +#try-bridge { + cursor: default; +} + +#try-bridge-icon { + list-style-image: url("chrome://browser/content/torconnect/bridge.svg"); +} + +button.primary { background-color: var(--purple-60)!important; - color: white; - fill: white; + color: white!important; + fill: white!important; }
-#connectButton:hover { +button.primary:hover { background-color: var(--purple-70)!important; - color: white; - fill: white; + color: white!important; + fill: white!important; }
-#connectButton:active { +button.primary:active { background-color: var(--purple-80)!important; - color: white; - fill: white; + color: white!important; + fill: white!important; +} + +div#locationDropdownLabel { + margin-block: auto; + margin-inline: 4px; +} + +/* these two follow similar css in error-pages.css for buttons */ +@media only screen and (min-width: 480px) { + form#locationDropdown { + margin-inline: 4px; + /* subtracting out the margin is needeed because by + default forms have different margins than buttons */ + max-width: calc(100% - 8px); + } +} + +@media only screen and (max-width: 480px) { + form#locationDropdown, + div#locationDropdownLabel { + margin: 0.66em 0 0; + } + + form#locationDropdown { + width: 100%; + } +} + +form#locationDropdown select { + max-width: 100%; + padding-block: 0; + margin-inline: 0; + font-weight: 700; }
/* checkbox css */ @@ -92,75 +203,28 @@ input[type="checkbox"]:not(:disabled):active:checked { vertical-align: middle; }
-#copyLogButton { - position: relative; -} - /* mirrors p element spacing */ -#copyLogContainer { +#viewLogContainer { margin: 1em 0; height: 1.2em; min-height: 1.2em; }
-#copyLogLink { +#viewLogLink { position: relative; display: inline-block; color: var(--in-content-link-color); }
/* hidden apparently only works if no display is set; who knew? */ -#copyLogLink[hidden="true"] { +#viewLogLink[hidden="true"] { display: none; }
-#copyLogLink:hover { +#viewLogLink:hover { cursor:pointer; }
-/* This div: - - is centered over its parent - - centers its child - - has z-index above parent - - ignores mouse events from parent -*/ -#copyLogTooltip { - pointer-events: none; - visibility: hidden; - display: flex; - justify-content: center; - white-space: nowrap; - width: 0; - position: absolute; - - z-index: 1; - left: 50%; - bottom: calc(100% + 0.25em); -} - -/* tooltip content (any content could go here) */ -#copyLogTooltipText { - background-color: var(--green-50); - color: var(--green-90); - border-radius: 2px; - padding: 4px; - line-height: 13px; - font: 11px sans-serif; - font-weight: 400; -} - -/* our speech bubble tail */ -#copyLogTooltipText::after { - content: ""; - position: absolute; - top: 100%; - left: 50%; - margin-left: -4px; - border-width: 4px; - border-style: solid; - border-color: var(--green-50) transparent transparent transparent; -} - body { padding: 0px !important; justify-content: space-between; @@ -175,6 +239,9 @@ body { }
.title.error { - background-image: url("chrome://browser/content/torconnect/onion-slash.svg"); + background-image: url("chrome://browser/content/torconnect/connection-failure.svg"); }
+.title.location { + background-image: url("chrome://browser/content/torconnect/connection-location.svg"); +} diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js index 26b17afb69383..8710eef95a49e 100644 --- a/browser/components/torconnect/content/aboutTorConnect.js +++ b/browser/components/torconnect/content/aboutTorConnect.js @@ -5,59 +5,139 @@ // populated in AboutTorConnect.init() let TorStrings = {}; let TorConnectState = {}; +let TorCensorshipLevel = {}; + +const BreadcrumbStatus = Object.freeze({ + Disabled: -1, + Default: 0, + Active: 1, + Error: 2, +});
class AboutTorConnect { selectors = Object.freeze({ textContainer: { title: "div.title", titleText: "h1.title-text", + longContentText: "#connectLongContentText", }, progress: { description: "p#connectShortDescText", meter: "div#progressBackground", }, - copyLog: { - link: "span#copyLogLink", - tooltip: "div#copyLogTooltip", - tooltipText: "span#copyLogTooltipText", + breadcrumbs: { + container: "#breadcrumbs", + connectionAssist: { + link: "#connection-assist", + label: "#connection-assist .breadcrumb-label", + }, + locationSettings: { + link: "#location-settings", + label: "#location-settings .breadcrumb-label", + }, + tryBridge: { + link: "#try-bridge", + label: "#try-bridge .breadcrumb-label", + }, + }, + viewLog: { + link: "span#viewLogLink", }, quickstart: { + container: "div#quickstartContainer", checkbox: "input#quickstartCheckbox", label: "label#quickstartCheckboxLabel", }, buttons: { - connect: "button#connectButton", + restart: "button#restartButton", + configure: "button#configureButton", cancel: "button#cancelButton", - advanced: "button#advancedButton", + connect: "button#connectButton", + tryBridge: "button#tryBridgeButton", + locationDropdownLabel: "div#locationDropdownLabel", + locationDropdown: "form#locationDropdown", + locationDropdownSelect: "form#locationDropdown select", + tryAgain: "button#tryAgainButton", }, - }) + });
elements = Object.freeze({ title: document.querySelector(this.selectors.textContainer.title), titleText: document.querySelector(this.selectors.textContainer.titleText), - progressDescription: document.querySelector(this.selectors.progress.description), + longContentText: document.querySelector( + this.selectors.textContainer.longContentText + ), + progressDescription: document.querySelector( + this.selectors.progress.description + ), progressMeter: document.querySelector(this.selectors.progress.meter), - copyLogLink: document.querySelector(this.selectors.copyLog.link), - copyLogTooltip: document.querySelector(this.selectors.copyLog.tooltip), - copyLogTooltipText: document.querySelector(this.selectors.copyLog.tooltipText), - quickstartCheckbox: document.querySelector(this.selectors.quickstart.checkbox), + breadcrumbContainer: document.querySelector( + this.selectors.breadcrumbs.container + ), + connectionAssistLink: document.querySelector( + this.selectors.breadcrumbs.connectionAssist.link + ), + connectionAssistLabel: document.querySelector( + this.selectors.breadcrumbs.connectionAssist.label + ), + locationSettingsLink: document.querySelector( + this.selectors.breadcrumbs.locationSettings.link + ), + locationSettingsLabel: document.querySelector( + this.selectors.breadcrumbs.locationSettings.label + ), + tryBridgeLink: document.querySelector( + this.selectors.breadcrumbs.tryBridge.link + ), + tryBridgeLabel: document.querySelector( + this.selectors.breadcrumbs.tryBridge.label + ), + viewLogLink: document.querySelector(this.selectors.viewLog.link), + quickstartContainer: document.querySelector( + this.selectors.quickstart.container + ), + quickstartCheckbox: document.querySelector( + this.selectors.quickstart.checkbox + ), quickstartLabel: document.querySelector(this.selectors.quickstart.label), - connectButton: document.querySelector(this.selectors.buttons.connect), + restartButton: document.querySelector(this.selectors.buttons.restart), + configureButton: document.querySelector(this.selectors.buttons.configure), cancelButton: document.querySelector(this.selectors.buttons.cancel), - advancedButton: document.querySelector(this.selectors.buttons.advanced), - }) + connectButton: document.querySelector(this.selectors.buttons.connect), + tryBridgeButton: document.querySelector(this.selectors.buttons.tryBridge), + locationDropdownLabel: document.querySelector( + this.selectors.buttons.locationDropdownLabel + ), + locationDropdown: document.querySelector( + this.selectors.buttons.locationDropdown + ), + locationDropdownSelect: document.querySelector( + this.selectors.buttons.locationDropdownSelect + ), + tryAgainButton: document.querySelector(this.selectors.buttons.tryAgain), + });
// a redirect url can be passed as a query parameter for the page to // forward us to once bootstrap completes (otherwise the window will just close) - redirect = null + redirect = null; + + locations = {};
beginBootstrap() { this.hide(this.elements.connectButton); + this.hide(this.elements.quickstartContainer); this.show(this.elements.cancelButton); this.elements.cancelButton.focus(); RPMSendAsyncMessage("torconnect:begin-bootstrap"); }
+ beginAutoBootstrap(countryCode) { + this.hide(this.elements.tryBridgeButton); + this.show(this.elements.cancelButton); + this.elements.cancelButton.focus(); + RPMSendAsyncMessage("torconnect:begin-autobootstrap", countryCode); + } + cancelBootstrap() { RPMSendAsyncMessage("torconnect:cancel-bootstrap"); } @@ -66,7 +146,12 @@ class AboutTorConnect { Element helper methods */
- show(element) { + show(element, primary) { + if (primary) { + element.classList.add("primary"); + } else { + element.classList.remove("primary"); + } element.removeAttribute("hidden"); }
@@ -74,15 +159,121 @@ class AboutTorConnect { element.setAttribute("hidden", "true"); }
- setTitle(title, error) { - this.elements.titleText.textContent = title; - document.title = title; + hideButtons() { + this.hide(this.elements.restartButton); + this.hide(this.elements.configureButton); + this.hide(this.elements.cancelButton); + this.hide(this.elements.connectButton); + this.hide(this.elements.tryBridgeButton); + this.hide(this.elements.locationDropdownLabel); + this.hide(this.elements.locationDropdown); + this.hide(this.elements.tryAgainButton); + } + + populateLocations() { + const selectCountryRegion = document.createElement("option"); + selectCountryRegion.textContent = TorStrings.torConnect.selectCountryRegion; + selectCountryRegion.value = ""; + + // get all codes and names from TorStrings + const locationNodes = []; + for (const [code, name] of Object.entries(this.locations)) { + let option = document.createElement("option"); + option.value = code; + option.textContent = name; + locationNodes.push(option); + } + // locale sort by name + locationNodes.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + + this.elements.locationDropdownSelect.append( + selectCountryRegion, + ...locationNodes + ); + } + + populateSpecialLocations(specialLocations) { + this.removeSpecialLocations(); + if (!specialLocations || !specialLocations.length) { + return; + } + + const locationNodes = []; + for (const code of specialLocations) { + const option = document.createElement("option"); + option.value = code; + + // codes (partially) come from rdsys service, so make sure we have a + // string defined for it + let name = this.locations[code]; + if (!name) { + name = code; + } + + option.textContent = name; + locationNodes.push(option); + } + // locale sort by name + locationNodes.sort((left, right) => + left.textContent.localeCompare(right.textContent) + ); + + const disabledDividerNode = document.createElement("option"); + disabledDividerNode.setAttribute("disabled", true); + disabledDividerNode.className = "divider"; + this.elements.locationDropdownSelect.options[0].after( + ...locationNodes, + disabledDividerNode + ); + }
- if (error) { - this.elements.title.classList.add("error"); + removeSpecialLocations() { + const select = this.elements.locationDropdownSelect; + if (select.querySelector(".divider") === null) { + return; + } + + while (select.options.length > 1) { + // Skip the "select country/region" option + const opt = select.options[1]; + opt.remove(); + if (opt.className === "divider") { + break; + } + } + } + + validateLocation() { + const selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + const selectedOption = this.elements.locationDropdownSelect.options[ + selectedIndex + ]; + if (!selectedOption.value) { + this.elements.tryAgainButton.setAttribute("disabled", "disabled"); } else { + this.elements.tryAgainButton.removeAttribute("disabled"); + } + } + + setTitle(title, className) { + this.elements.titleText.textContent = title; + if (className !== "error") { this.elements.title.classList.remove("error"); } + if (className !== "location") { + this.elements.title.classList.remove("location"); + } + if (className) { + this.elements.title.classList.add(className); + } + document.title = title; + } + + setLongText(...args) { + this.elements.longContentText.textContent = ""; + this.elements.longContentText.append(...args); }
setProgress(description, visible, percent) { @@ -95,13 +286,40 @@ class AboutTorConnect { } }
+ setBreadcrumbsStatus(connectionAssist, locationSettings, tryBridge) { + this.elements.breadcrumbContainer.classList.remove("hidden"); + let elems = [ + [this.elements.connectionAssistLink, connectionAssist], + [this.elements.locationSettingsLink, locationSettings], + [this.elements.tryBridgeLink, tryBridge], + ]; + elems.forEach(([elem, status]) => { + elem.classList.remove("disabled"); + elem.classList.remove("active"); + elem.classList.remove("error"); + switch (status) { + case BreadcrumbStatus.Disabled: + elem.classList.add("disabled"); + break; + case BreadcrumbStatus.Active: + elem.classList.add("active"); + break; + case BreadcrumbStatus.Error: + elem.classList.add("error"); + break; + } + }); + } + + hideBreadcrumbs() { + this.elements.breadcrumbContainer.classList.add("hidden"); + } + /* These methods update the UI based on the current TorConnect state */
updateUI(state) { - console.log(state); - // calls update_$state() this[`update_${state.State}`](state); this.elements.quickstartCheckbox.checked = state.QuickStartEnabled; @@ -113,82 +331,130 @@ class AboutTorConnect { const hasError = false; const showProgressbar = false;
- this.setTitle(TorStrings.torConnect.torConnect, hasError); - this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar); - this.hide(this.elements.copyLogLink); - this.hide(this.elements.connectButton); - this.hide(this.elements.advancedButton); - this.hide(this.elements.cancelButton); + this.setTitle(TorStrings.torConnect.torConnect, hasError ? "error" : ""); + this.setProgress( + TorStrings.settings.torPreferencesDescription, + showProgressbar + ); + this.hide(this.elements.quickstartContainer); + this.hide(this.elements.viewLogLink); + this.hideButtons(); }
update_Configuring(state) { const hasError = state.ErrorMessage != null; const showProgressbar = false;
+ this.hide(this.elements.quickstartContainer); + this.hide(this.elements.viewLogLink); + this.hideButtons(); + if (hasError) { - this.setTitle(state.ErrorMessage, hasError); - this.setProgress(state.ErrorDetails, showProgressbar); - this.show(this.elements.copyLogLink); - this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain; + switch (state.DetectedCensorshiplevel) { + case TorCensorshipLevel.None: + // we shouldn't be able to get here + break; + case TorCensorshipLevel.Moderate: + // bootstrap failed once, offer auto bootstrap + this.showConnectionAssistant(state.ErrorDetails); + if (state.StateChanged) { + this.elements.tryBridgeButton.focus(); + } + break; + case TorCensorshipLevel.Severe: + // autobootstrap failed, verify correct location + this.showLocationSettings(state.CountryCodes, state.ErrorMessage); + if (state.StateChanged) { + this.elements.tryAgainButton.focus(); + } + break; + case TorCensorshipLevel.Extreme: + // finally offer to restart tor-browser or go to configure options + this.showFinalError(state); + break; + } } else { - this.setTitle(TorStrings.torConnect.torConnect, hasError); - this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar); - this.hide(this.elements.copyLogLink); - this.elements.connectButton.textContent = TorStrings.torConnect.torConnectButton; - } - this.show(this.elements.connectButton); - if (state.StateChanged) { - this.elements.connectButton.focus(); + this.setTitle(TorStrings.torConnect.torConnect, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", showProgressbar); + this.show(this.elements.quickstartContainer); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + if (state.StateChanged) { + this.elements.connectButton.focus(); + } + this.elements.connectButton.textContent = + TorStrings.torConnect.torConnectButton; } - this.show(this.elements.advancedButton); - this.hide(this.elements.cancelButton); }
update_AutoBootstrapping(state) { - // TODO: noop until this state is used + const showProgressbar = true; + + if (state.DetectedCensorshiplevel >= TorCensorshipLevel.Severe) { + this.setTitle(TorStrings.torConnect.tryingBridgeAgain, ""); + } else { + this.setTitle(TorStrings.torConnect.tryingBridge, ""); + } + this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); + this.setProgress( + state.BootstrapStatus, + showProgressbar, + state.BootstrapProgress + ); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Active + ); + if (state.ShowViewLog) { + this.show(this.elements.viewLogLink); + } else { + this.hide(this.elements.viewLogLink); + } + this.hideButtons(); + this.show(this.elements.cancelButton, true); + if (state.StateChanged) { + this.elements.cancelButton.focus(); + } }
update_Bootstrapping(state) { - const hasError = false; const showProgressbar = true;
- this.setTitle(state.BootstrapStatus ? state.BootstrapStatus : TorStrings.torConnect.torConnecting, hasError); - this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar, state.BootstrapProgress); - if (state.ShowCopyLog) { - this.show(this.elements.copyLogLink); + this.setTitle(TorStrings.torConnect.torConnecting, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", showProgressbar, state.BootstrapProgress); + this.hideBreadcrumbs(); + if (state.ShowViewLog) { + this.show(this.elements.viewLogLink); } else { - this.hide(this.elements.copyLogLink); + this.hide(this.elements.viewLogLink); } - this.hide(this.elements.connectButton); - this.hide(this.elements.advancedButton); - this.show(this.elements.cancelButton); + this.hideButtons(); + this.show(this.elements.cancelButton, true); if (state.StateChanged) { this.elements.cancelButton.focus(); } }
update_Error(state) { - const hasError = true; const showProgressbar = false;
- this.setTitle(state.ErrorMessage, hasError); + this.setTitle(state.ErrorMessage, "error"); + this.setLongText(""); this.setProgress(state.ErrorDetails, showProgressbar); - this.show(this.elements.copyLogLink); - this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain; - this.show(this.elements.connectButton); - this.show(this.elements.advancedButton); - this.hide(this.elements.cancelButton); + this.hideButtons(); + this.show(this.elements.viewLogLink); }
update_Bootstrapped(state) { - const hasError = false; const showProgressbar = true;
- this.setTitle(TorStrings.torConnect.torConnected, hasError); - this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar, 100); - this.hide(this.elements.connectButton); - this.hide(this.elements.advancedButton); - this.hide(this.elements.cancelButton); + this.setTitle(TorStrings.torConnect.torConnected, ""); + this.setLongText(TorStrings.settings.torPreferencesDescription); + this.setProgress("", showProgressbar, 100); + this.hideButtons();
// redirects page to the requested redirect url, removes about:torconnect // from the page stack, so users cannot accidentally go 'back' to the @@ -201,45 +467,129 @@ class AboutTorConnect { // it isn't in use (eg using tor-launcher or system tor) }
- async initElements(direction) { + showConnectionAssistant(error) { + const hasError = !!error; + this.setTitle( + TorStrings.torConnect.couldNotConnect, + hasError ? "error" : "" + ); + this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription); + this.setProgress(error, false); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Active, + BreadcrumbStatus.Default, + BreadcrumbStatus.Disabled + ); + this.hideButtons(); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton); + this.show(this.elements.tryBridgeButton, true); + } + + showConfigureConnectionLink(text) { + const pieces = text.split("#1"); + const link = document.createElement("a"); + link.textContent = TorStrings.torConnect.configureConnection; + link.setAttribute("href", "#"); + link.addEventListener("click", e => { + e.preventDefault(); + RPMSendAsyncMessage("torconnect:open-tor-preferences"); + }); + this.setLongText(pieces[0], link, pieces[1]); + }
+ showLocationSettings(locations, error) { + const hasError = !!error; + if (hasError) { + this.setTitle(TorStrings.torConnect.errorLocation, "location"); + this.setLongText(TorStrings.torConnect.errorLocationDescription); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Disabled, + BreadcrumbStatus.Error, + BreadcrumbStatus.Disabled + ); + this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; + } else { + this.setTitle(TorStrings.torConnect.addLocation, "location"); + this.showConfigureConnectionLink( + TorStrings.torConnect.addLocationDescription + ); + this.setBreadcrumbsStatus( + BreadcrumbStatus.Default, + BreadcrumbStatus.Active, + BreadcrumbStatus.Disabled + ); + this.elements.tryAgainButton.textContent = + TorStrings.torConnect.tryBridge; + } + this.setProgress(error, false); + this.hideButtons(); + if (!locations || !locations.length) { + RPMSendQuery("torconnect:get-country-codes").then(codes => { + if (codes && codes.length) { + this.populateSpecialLocations(codes); + } + }); + } else { + this.populateSpecialLocations(locations); + } + this.validateLocation(); + this.show(this.elements.locationDropdownLabel); + this.show(this.elements.locationDropdown); + this.show(this.elements.tryAgainButton, true); + } + + showFinalError(state) { + this.setTitle(TorStrings.torConnect.finalError, "error"); + this.setLongText(TorStrings.torConnect.finalErrorDescription); + this.setProgress(state ? state.ErrorDetails : "", false); + this.hideButtons(); + this.show(this.elements.restartButton); + this.show(this.elements.configureButton); + this.show(this.elements.connectButton, true); + } + + initElements(direction) { document.documentElement.setAttribute("dir", direction);
- // sets the text content while keeping the child elements intact - this.elements.copyLogLink.childNodes[0].nodeValue = - TorStrings.torConnect.copyLog; - this.elements.copyLogLink.addEventListener("click", async (event) => { - const copiedMessage = await RPMSendQuery("torconnect:copy-tor-logs"); - this.elements.copyLogTooltipText.textContent = copiedMessage; - this.elements.copyLogTooltipText.style.visibility = "visible"; - - // clear previous timeout if one already exists - if (this.copyLogTimeoutId) { - clearTimeout(this.copyLogTimeoutId); + this.elements.connectionAssistLink.addEventListener("click", event => { + if (!this.elements.connectionAssistLink.classList.contains("disabled")) { + this.showConnectionAssistant(); } - - // hide tooltip after X ms - const TOOLTIP_TIMEOUT = 2000; - this.copyLogTimeoutId = setTimeout(() => { - this.elements.copyLogTooltipText.style.visibility = "hidden"; - this.copyLogTimeoutId = 0; - }, TOOLTIP_TIMEOUT); + }); + this.elements.connectionAssistLabel.textContent = + TorStrings.torConnect.breadcrumbAssist; + this.elements.locationSettingsLink.addEventListener("click", event => { + if (!this.elements.connectionAssistLink.classList.contains("disabled")) { + this.showLocationSettings(); + } + }); + this.elements.locationSettingsLabel.textContent = + TorStrings.torConnect.breadcrumbLocation; + this.elements.tryBridgeLabel.textContent = + TorStrings.torConnect.breadcrumbTryBridge; + + this.elements.viewLogLink.textContent = TorStrings.torConnect.viewLog; + this.elements.viewLogLink.addEventListener("click", event => { + RPMSendAsyncMessage("torconnect:view-tor-logs"); });
this.elements.quickstartCheckbox.addEventListener("change", () => { const quickstart = this.elements.quickstartCheckbox.checked; RPMSendAsyncMessage("torconnect:set-quickstart", quickstart); }); - this.elements.quickstartLabel.textContent = TorStrings.settings.quickstartCheckbox; + this.elements.quickstartLabel.textContent = + TorStrings.settings.quickstartCheckbox;
- this.elements.connectButton.textContent = - TorStrings.torConnect.torConnectButton; - this.elements.connectButton.addEventListener("click", () => { - this.beginBootstrap(); + this.elements.restartButton.textContent = + TorStrings.torConnect.restartTorBrowser; + this.elements.restartButton.addEventListener("click", () => { + RPMSendAsyncMessage("torconnect:restart"); });
- this.elements.advancedButton.textContent = TorStrings.torConnect.torConfigure; - this.elements.advancedButton.addEventListener("click", () => { + this.elements.configureButton.textContent = + TorStrings.torConnect.torConfigure; + this.elements.configureButton.addEventListener("click", () => { RPMSendAsyncMessage("torconnect:open-tor-preferences"); });
@@ -247,6 +597,36 @@ class AboutTorConnect { this.elements.cancelButton.addEventListener("click", () => { this.cancelBootstrap(); }); + + this.elements.connectButton.textContent = + TorStrings.torConnect.torConnectButton; + this.elements.connectButton.addEventListener("click", () => { + this.beginBootstrap(); + }); + + this.populateLocations(); + this.elements.locationDropdownSelect.addEventListener("change", () => { + this.validateLocation(); + }); + + this.elements.tryBridgeButton.textContent = TorStrings.torConnect.tryBridge; + this.elements.tryBridgeButton.addEventListener("click", () => { + this.beginAutoBootstrap(); + }); + + this.elements.locationDropdownLabel.textContent = + TorStrings.torConnect.yourLocation; + + this.elements.tryAgainButton.textContent = TorStrings.torConnect.tryAgain; + this.elements.tryAgainButton.setAttribute("disabled", "disabled"); + this.elements.tryAgainButton.addEventListener("click", () => { + let selectedIndex = this.elements.locationDropdownSelect.selectedIndex; + let selectedOption = this.elements.locationDropdownSelect.options[ + selectedIndex + ]; + + this.beginAutoBootstrap(selectedOption.value); + }); }
initObservers() { @@ -257,7 +637,7 @@ class AboutTorConnect { }
initKeyboardShortcuts() { - document.onkeydown = (evt) => { + document.onkeydown = evt => { // unfortunately it looks like we still haven't standardized keycodes to // integers, so we must resort to a string compare here :( // see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for relevant documentation @@ -284,6 +664,8 @@ class AboutTorConnect { // various constants TorStrings = Object.freeze(args.TorStrings); TorConnectState = Object.freeze(args.TorConnectState); + TorCensorshipLevel = Object.freeze(args.TorCensorshipLevel); + this.locations = args.CountryNames;
this.initElements(args.Direction); this.initObservers(); diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml index 595bbdf9a70a1..a98af43e2d53f 100644 --- a/browser/components/torconnect/content/aboutTorConnect.xhtml +++ b/browser/components/torconnect/content/aboutTorConnect.xhtml @@ -9,22 +9,35 @@ <body> <div id="progressBackground"></div> <div id="connectPageContainer" class="container"> + <div id="breadcrumbs" class="hidden"> + <span id="connection-assist" class="breadcrumb-item"> + <span id="connection-assist-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + <span class="breadcrumb-separator breadcrumb-icon" /> + <span id="location-settings" class="breadcrumb-item"> + <span id="location-settings-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + <span class="breadcrumb-separator breadcrumb-icon" /> + <span id="try-bridge" class="breadcrumb-item"> + <span id="try-bridge-icon" class="breadcrumb-icon" /> + <span class="breadcrumb-label"/> + </span> + </div> <div id="text-container"> <div class="title"> <h1 class="title-text"/> </div> <div id="connectLongContent"> - <div id="connectShortDesc"> - <p id="connectShortDescText" /> - </div> + <p id="connectLongContentText" /> + </div> + <div id="connectShortDesc"> + <p id="connectShortDescText" /> </div>
- <div id="copyLogContainer"> - <span id="copyLogLink" hidden="true"> - <div id="copyLogTooltip"> - <span id="copyLogTooltipText"/> - </div> - </span> + <div id="viewLogContainer"> + <span id="viewLogLink" hidden="true"></span> </div>
<div id="quickstartContainer"> @@ -33,9 +46,17 @@ </div>
<div id="connectButtonContainer" class="button-container"> - <button id="advancedButton" hidden="true"></button> + <button id="restartButton" hidden="true"></button> + <button id="configureButton" hidden="true"></button> <button id="cancelButton" hidden="true"></button> - <button id="connectButton" class="primary try-again" hidden="true"></button> + <button id="connectButton" class="primary" hidden="true"></button> + <button id="tryBridgeButton" class="primary" hidden="true"></button> + <div id="locationDropdownLabel"/> + <form id="locationDropdown" hidden="true"> + <select id="countries"> + </select> + </form> + <button id="tryAgainButton" class="primary" hidden="true"></button> </div> </div> </div> diff --git a/browser/components/torconnect/content/arrow-right.svg b/browser/components/torconnect/content/arrow-right.svg new file mode 100644 index 0000000000000..3f6d8ded52bed --- /dev/null +++ b/browser/components/torconnect/content/arrow-right.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M10.9991 8.352L5.53406 13.818C5.41557 13.9303 5.25792 13.9918 5.09472 13.9895C4.93152 13.9872 4.77567 13.9212 4.66039 13.8057C4.54511 13.6902 4.47951 13.5342 4.47758 13.3709C4.47565 13.2077 4.53754 13.0502 4.65006 12.932L9.58506 7.998L4.65106 3.067C4.53868 2.94864 4.47697 2.79106 4.47909 2.62786C4.48121 2.46466 4.54698 2.30874 4.66239 2.19333C4.7778 2.07792 4.93372 2.01215 5.09692 2.01003C5.26012 2.00792 5.41769 2.06962 5.53606 2.182L11.0001 7.647L10.9991 8.352Z" fill="conte [...] +</svg> diff --git a/browser/components/torconnect/content/bridge.svg b/browser/components/torconnect/content/bridge.svg new file mode 100644 index 0000000000000..5ae3f05dfd082 --- /dev/null +++ b/browser/components/torconnect/content/bridge.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M1 9.48528C1 9.48528 3.82843 9.48528 6.65685 6.65685C9.48528 3.82843 9.48528 1 9.48528 1" stroke="context-fill" stroke-width="1.25" stroke-linecap="round"/> + <path d="M6.65686 15.1421C6.65686 15.1421 6.65686 12.3137 9.48529 9.48529C12.3137 6.65686 15.1421 6.65686 15.1421 6.65686" stroke="context-fill" stroke-width="1.25" stroke-linecap="round"/> +</svg> diff --git a/browser/components/torconnect/content/connection-failure.svg b/browser/components/torconnect/content/connection-failure.svg new file mode 100644 index 0000000000000..8f2005e360556 --- /dev/null +++ b/browser/components/torconnect/content/connection-failure.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <path fill="context-fill" d="M 30,1.875 C 14.467,1.875 1.875,14.467 1.875,30 c 0,6.725546 2.3647525,12.894963 6.3027344,17.734375 l -4.7636719,4.763672 c -0.7834743,0.783474 -0.7834743,2.044651 0,2.828125 0.7834743,0.783474 2.0446507,0.783474 2.828125,0 C 21.046044,40.52782 34.415343,27.146014 47.546875,14.023438 v -0.002 l 6.779297,-6.7792965 c 0.783474,-0.7834743 0.783474,-2.0446507 0,-2.828125 -0.783474,-0.7834743 -2.044651,-0.7834743 -2.828125,0 L 47.734375,8.1777344 C 42.894963,4. [...] + <path fill="#d70022" d="m59.5328 52.4973-10.261-18.5715c-.7112-1.2833-1.9917-1.9258-3.2722-1.9258-1.2806 0-2.5611.6425-3.2704 1.9258l-10.261 18.5715c-1.3701 2.4755.4312 5.5027 3.2704 5.5027h20.5238c2.8373 0 4.6387-3.0272 3.2704-5.5027zm-12.3666-.533-.4666.4642h-1.4l-.4667-.4642v-1.3929l.4667-.4643h1.4l.4666.4643zm0-4.992c0 .3078-.1229.603-.3417.8207s-.5155.34-.8249.34-.6062-.1223-.825-.34-.3417-.5129-.3417-.8207v-6.383c0-.3079.1229-.6031.3417-.8208s.5156-.34.825-.34.6061.1223.8249.34.3 [...] +</svg> diff --git a/browser/components/torconnect/content/connection-location.svg b/browser/components/torconnect/content/connection-location.svg new file mode 100644 index 0000000000000..1e5c41ccf99a0 --- /dev/null +++ b/browser/components/torconnect/content/connection-location.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg"> + <path fill="context-fill" d="M 30,1.875 C 14.467,1.875 1.875,14.467 1.875,30 c 0,6.725546 2.3647429,12.894963 6.3027344,17.734375 l -4.7636719,4.763672 c -0.7834743,0.783474 -0.7834743,2.044651 0,2.828125 0.7834743,0.783474 2.0446507,0.783474 2.828125,0 C 21.049647,40.524244 34.416498,27.144859 47.546875,14.023438 v -0.002 l 6.779297,-6.7792965 c 0.783474,-0.7834743 0.783474,-2.0446507 0,-2.828125 -0.783474,-0.7834743 -2.044651,-0.7834743 -2.828125,0 L 47.734375,8.1777344 C 42.894963,4 [...] + <path fill="#ffa436" d="m45 30c-3.713 0-7.274 1.475-9.8995 4.1005s-4.1005 6.1865-4.1005 9.8995 1.475 7.274 4.1005 9.8995 6.1865 4.1005 9.8995 4.1005 7.274-1.475 9.8995-4.1005 4.1005-6.1865 4.1005-9.8995-1.475-7.274-4.1005-9.8995-6.1865-4.1005-9.8995-4.1005zm4.5677 3.2667c1.9167.8229 3.5778 2.1443 4.8108 3.8267 1.233 1.6825 1.9928 3.6644 2.2004 5.7399h-4.1608c-.2298-3.4759-1.4862-6.8054-3.6101-9.5666zm-3.8248 0c2.5257 2.5792 4.06 5.967 4.3326 9.5666h-10.151c.2726-3.5996 1.8069-6.9874 4. [...] +</svg> diff --git a/browser/components/torconnect/content/globe.svg b/browser/components/torconnect/content/globe.svg new file mode 100644 index 0000000000000..f4d1f19b43ce8 --- /dev/null +++ b/browser/components/torconnect/content/globe.svg @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M8 0.5C6.01088 0.5 4.10322 1.29018 2.6967 2.6967C1.29018 4.10322 0.5 6.01088 0.5 8C0.5 9.98912 1.29018 11.8968 2.6967 13.3033C4.10322 14.7098 6.01088 15.5 8 15.5C9.98912 15.5 11.8968 14.7098 13.3033 13.3033C14.7098 11.8968 15.5 9.98912 15.5 8C15.5 6.01088 14.7098 4.10322 13.3033 2.6967C11.8968 1.29018 9.98912 0.5 8 0.5ZM10.447 2.25C11.4738 2.69088 12.3637 3.39877 13.0242 4.30006C13.6848 5.20135 14.0918 6.26313 14.203 7.375H11.974C11.8509 5.51288 11.1778 3.72922 10.04 2.25H10 [...] +</svg> diff --git a/browser/components/torconnect/content/onion-slash-fillable.svg b/browser/components/torconnect/content/onion-slash-fillable.svg new file mode 100644 index 0000000000000..18f1c5a5520bd --- /dev/null +++ b/browser/components/torconnect/content/onion-slash-fillable.svg @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg"> + <path d="m14.1161 15.6245c-.0821.0001-.1634-.016-.2393-.0474-.0758-.0314-.1447-.0775-.2027-.1356l-12.749984-12.749c-.109266-.11882-.168406-.27526-.165071-.43666.003335-.16139.068886-.31525.182967-.42946.114078-.11421.267868-.17994.429258-.18345.16139-.00352.3179.05544.43685.16457l12.74998 12.75c.1168.1176.1824.2767.1824.4425s-.0656.3249-.1824.4425c-.058.058-.1269.1039-.2028.1352-.0759.0312-.1571.0471-.2392.0468z" fill-opacity="context-fill-opacity" fill="context-fill" /> + <path d="m 8,0.5000002 c -1.61963,0 -3.1197431,0.5137987 -4.3457031,1.3867188 l 0.84375,0.8417968 0.7792969,0.78125 0.8613281,0.8613282 0.8164062,0.8164062 0.9863281,0.984375 h 0.058594 c 1.00965,0 1.828125,0.818485 1.828125,1.828125 0,0.01968 6.2e-4,0.039074 0,0.058594 L 10.8125,9.0449221 C 10.9334,8.7195921 11,8.3674002 11,8.0000002 c 0,-1.65685 -1.34314,-3 -3,-3 v -1.078125 c 2.25231,0 4.078125,1.825845 4.078125,4.078125 0,0.67051 -0.162519,1.3033281 -0.449219,1.8613281 l 0.861328,0 [...] +</svg> diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn index ed8a4de299b2b..8ca0b0651523e 100644 --- a/browser/components/torconnect/jar.mn +++ b/browser/components/torconnect/jar.mn @@ -3,5 +3,11 @@ browser.jar: content/browser/torconnect/aboutTorConnect.css (content/aboutTorConnect.css) * content/browser/torconnect/aboutTorConnect.xhtml (content/aboutTorConnect.xhtml) content/browser/torconnect/aboutTorConnect.js (content/aboutTorConnect.js) + content/browser/torconnect/arrow-right.svg (content/arrow-right.svg) + content/browser/torconnect/bridge.svg (content/bridge.svg) + content/browser/torconnect/globe.svg (content/globe.svg) + content/browser/torconnect/connection-failure.svg (content/connection-failure.svg) + content/browser/torconnect/connection-location.svg (content/connection-location.svg) content/browser/torconnect/onion.svg (content/onion.svg) content/browser/torconnect/onion-slash.svg (content/onion-slash.svg) + content/browser/torconnect/onion-slash-fillable.svg (content/onion-slash-fillable.svg) diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index c0ead80fb2f67..5ddf546ce12de 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -217,23 +217,22 @@ let RemotePageAccessManager = { RPMRemoveMessageListener: ["*"], }, "about:tbupdate": { - RPMSendQuery: [ - "FetchUpdateData", - ], + RPMSendQuery: ["FetchUpdateData"], }, "about:torconnect": { - RPMAddMessageListener: [ - "torconnect:state-change", - ], + RPMAddMessageListener: ["torconnect:state-change"], RPMSendAsyncMessage: [ "torconnect:open-tor-preferences", "torconnect:begin-bootstrap", + "torconnect:begin-autobootstrap", "torconnect:cancel-bootstrap", "torconnect:set-quickstart", + "torconnect:view-tor-logs", + "torconnect:restart", ], RPMSendQuery: [ "torconnect:get-init-args", - "torconnect:copy-tor-logs", + "torconnect:get-country-codes", ], }, },
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.8.0esr-11.5-1 in repository tor-browser.
commit 623e8a73c697653e0bca3ff5a50b8e8235a1358d Author: Richard Pospesel richard@torproject.org AuthorDate: Tue Apr 5 20:37:57 2022 +0000
fixup! Bug 40597: Implement TorSettings module
saving received bridge strings to prefs --- browser/modules/TorSettings.jsm | 1 + 1 file changed, 1 insertion(+)
diff --git a/browser/modules/TorSettings.jsm b/browser/modules/TorSettings.jsm index 87bc129602930..41638f9cbd1b6 100644 --- a/browser/modules/TorSettings.jsm +++ b/browser/modules/TorSettings.jsm @@ -538,6 +538,7 @@ const TorSettings = (() => { if (settings.bridges.bridge_strings.length == 0 && settings.bridges.enabled) { throw new Error(`No available builtin bridges of type ${settings.bridges.builtin_type}`); } + this._settings.bridges.bridge_strings = settings.bridges.bridge_strings; break; } case TorBridgeSource.Invalid:
tbb-commits@lists.torproject.org