tbb-commits
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- 1 participants
- 21159 discussions
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 28044: Integrate Tor Launcher into tor-browser
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 4290bdb7662f3b4970e1b5c0d3490976dd38ab7f
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Tue Feb 26 10:07:17 2019 -0500
Bug 28044: Integrate Tor Launcher into tor-browser
Build and package Tor Launcher as part of the browser (similar to
how pdfjs is handled).
If a Tor Launcher extension is present in the user's profile, it is
removed.
---
.mozconfig | 2 +-
browser/extensions/moz.build | 3 +++
browser/installer/package-manifest.in | 5 +++++
toolkit/mozapps/extensions/internal/XPIProvider.jsm | 10 ++++++++++
4 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/.mozconfig b/.mozconfig
index 18cd1f9b6487..7fe8633a9ef4 100755
--- a/.mozconfig
+++ b/.mozconfig
@@ -34,6 +34,6 @@ ac_add_options --enable-proxy-bypass-protection
# Disable telemetry
ac_add_options MOZ_TELEMETRY_REPORTING=
-ac_add_options --disable-tor-launcher
+ac_add_options --enable-tor-launcher
ac_add_options --with-tor-browser-version=dev-build
ac_add_options --disable-tor-browser-update
diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build
index 339702b90a8a..d76a9f93d9af 100644
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -10,3 +10,6 @@ if CONFIG["NIGHTLY_BUILD"]:
DIRS += [
"translations",
]
+
+if not CONFIG["TOR_BROWSER_DISABLE_TOR_LAUNCHER"]:
+ DIRS += ["tor-launcher"]
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index d4068cabb4ae..d46707ca8720 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -239,6 +239,11 @@
@RESPATH@/browser/chrome/browser.manifest
@RESPATH@/chrome/pdfjs.manifest
@RESPATH@/chrome/pdfjs/*
+#ifndef TOR_BROWSER_DISABLE_TOR_LAUNCHER
+@RESPATH@/browser/chrome/torlauncher.manifest
+@RESPATH@/browser/chrome/torlauncher/*
+@RESPATH@/browser/@PREF_DIR@/torlauncher-prefs.js
+#endif
@RESPATH@/chrome/torbutton.manifest
@RESPATH@/chrome/torbutton/*
@RESPATH@/chrome/toolkit@JAREXT@
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 8e16e236b238..04d57a42348e 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1485,6 +1485,16 @@ var XPIStates = {
continue;
}
+ // Since it is now part of the browser, uninstall the Tor Launcher
+ // extension. This will remove the Tor Launcher .xpi from user
+ // profiles on macOS.
+ if (id === "tor-launcher(a)torproject.org") {
+ logger.debug("Uninstalling the Tor Launcher extension.");
+ loc.installer.uninstallAddon(id);
+ changed = true;
+ continue;
+ }
+
let xpiState = loc.get(id);
if (!xpiState) {
// If the location is not supported for sideloading, skip new
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 27476: Implement about:torconnect captive portal within Tor Browser
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 3eae5a64d0b9598ad9f12c5c2afe55462bac0d42
Author: Richard Pospesel <richard(a)torproject.org>
Date: Wed Apr 28 23:09:34 2021 -0500
Bug 27476: Implement about:torconnect captive portal within Tor Browser
- implements new about:torconnect page as tor-launcher replacement
- adds tor connection status to url bar and tweaks UX when not online
- adds new torconnect component to browser
- tor process management functionality remains implemented in tor-launcher through the TorProtocolService module
- adds warning/error box to about:preferences#tor when not connected to tor
- explicitly allows about:torconnect URIs to ignore Resist Fingerprinting (RFP)
- various tweaks to info-pages.inc.css for about:torconnect (also affects other firefox info pages)
---
browser/actors/NetErrorParent.jsm | 8 +
browser/base/content/browser-siteIdentity.js | 2 +-
browser/base/content/browser.js | 66 +++--
browser/base/content/browser.xhtml | 2 +
browser/base/content/certerror/aboutNetError.js | 12 +-
browser/base/content/navigator-toolbox.inc.xhtml | 1 +
browser/base/content/utilityOverlay.js | 14 +
browser/components/BrowserGlue.jsm | 14 +
browser/components/about/AboutRedirector.cpp | 4 +
browser/components/about/components.conf | 1 +
browser/components/moz.build | 1 +
browser/components/sessionstore/SessionStore.jsm | 4 +
browser/components/torconnect/TorConnectChild.jsm | 9 +
browser/components/torconnect/TorConnectParent.jsm | 147 ++++++++++
.../torconnect/content/aboutTorConnect.css | 180 +++++++++++++
.../torconnect/content/aboutTorConnect.js | 298 +++++++++++++++++++++
.../torconnect/content/aboutTorConnect.xhtml | 45 ++++
.../components/torconnect/content/onion-slash.svg | 5 +
browser/components/torconnect/content/onion.svg | 4 +
.../torconnect/content/torBootstrapUrlbar.js | 93 +++++++
.../torconnect/content/torconnect-urlbar.css | 57 ++++
.../torconnect/content/torconnect-urlbar.inc.xhtml | 10 +
browser/components/torconnect/jar.mn | 7 +
browser/components/torconnect/moz.build | 6 +
browser/components/urlbar/UrlbarInput.jsm | 32 +++
browser/modules/TorProcessService.jsm | 12 +
browser/modules/TorStrings.jsm | 80 ++++++
browser/modules/moz.build | 2 +
browser/themes/shared/urlbar-searchbar.inc.css | 3 +
dom/base/Document.cpp | 51 +++-
dom/base/nsGlobalWindowOuter.cpp | 2 +
.../processsingleton/MainProcessSingleton.jsm | 5 +
toolkit/modules/RemotePageAccessManager.jsm | 16 ++
toolkit/mozapps/update/UpdateService.jsm | 68 ++++-
.../lib/environments/browser-window.js | 4 +
35 files changed, 1237 insertions(+), 28 deletions(-)
diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm
index 3472c68f664a..13afbbbfd4a8 100644
--- a/browser/actors/NetErrorParent.jsm
+++ b/browser/actors/NetErrorParent.jsm
@@ -21,6 +21,10 @@ const { TelemetryController } = ChromeUtils.import(
"resource://gre/modules/TelemetryController.jsm"
);
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+
const PREF_SSL_IMPACT_ROOTS = [
"security.tls.version.",
"security.ssl3.",
@@ -350,6 +354,10 @@ class NetErrorParent extends JSWindowActorParent {
break;
}
}
+ break;
+ case "ShouldShowTorConnect":
+ return TorConnect.shouldShowTorConnect;
}
+ return undefined;
}
}
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index 2846a1cb2fcf..6901ce71814a 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -57,7 +57,7 @@ var gIdentityHandler = {
* RegExp used to decide if an about url should be shown as being part of
* the browser UI.
*/
- _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback)(?:[?#]|$)/i,
+ _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i,
/**
* Whether the established HTTPS connection is considered "broken".
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index f823ce536c5d..9fe5302c5f58 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -80,6 +80,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabModalPrompt: "chrome://global/content/tabprompts.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
+ TorConnect: "resource:///modules/TorConnect.jsm",
Translation: "resource:///modules/translation/TranslationParent.jsm",
UITour: "resource:///modules/UITour.jsm",
UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
@@ -634,6 +635,7 @@ var gPageIcons = {
var gInitialPages = [
"about:tor",
+ "about:torconnect",
"about:blank",
"about:newtab",
"about:home",
@@ -1838,6 +1840,8 @@ var gBrowserInit = {
}
this._loadHandled = true;
+
+ TorBootstrapUrlbar.init();
},
_cancelDelayedStartup() {
@@ -2386,32 +2390,48 @@ var gBrowserInit = {
let defaultArgs = BrowserHandler.defaultArgs;
- // If the given URI is different from the homepage, we want to load it.
- if (uri != defaultArgs) {
- AboutNewTab.noteNonDefaultStartup();
+ // figure out which URI to actually load (or a Promise to get the uri)
+ uri = ((uri) => {
+ // If the given URI is different from the homepage, we want to load it.
+ if (uri != defaultArgs) {
+ AboutNewTab.noteNonDefaultStartup();
+
+ if (uri instanceof Ci.nsIArray) {
+ // Transform the nsIArray of nsISupportsString's into a JS Array of
+ // JS strings.
+ return Array.from(
+ uri.enumerate(Ci.nsISupportsString),
+ supportStr => supportStr.data
+ );
+ } else if (uri instanceof Ci.nsISupportsString) {
+ return uri.data;
+ }
+ return uri;
+ }
- if (uri instanceof Ci.nsIArray) {
- // Transform the nsIArray of nsISupportsString's into a JS Array of
- // JS strings.
- return Array.from(
- uri.enumerate(Ci.nsISupportsString),
- supportStr => supportStr.data
- );
- } else if (uri instanceof Ci.nsISupportsString) {
- return uri.data;
+ // The URI appears to be the the homepage. We want to load it only if
+ // session restore isn't about to override the homepage.
+ let willOverride = SessionStartup.willOverrideHomepage;
+ if (typeof willOverride == "boolean") {
+ return willOverride ? null : uri;
}
- return uri;
- }
+ return willOverride.then(willOverrideHomepage =>
+ willOverrideHomepage ? null : uri
+ );
+ })(uri);
+
+ // if using TorConnect, convert these uris to redirects
+ if (TorConnect.shouldShowTorConnect) {
+ return Promise.resolve(uri).then((uri) => {
+ if (uri == null) {
+ uri = [];
+ }
- // The URI appears to be the the homepage. We want to load it only if
- // session restore isn't about to override the homepage.
- let willOverride = SessionStartup.willOverrideHomepage;
- if (typeof willOverride == "boolean") {
- return willOverride ? null : uri;
+ uri = TorConnect.getURIsToLoad(uri);
+ return uri;
+ });
}
- return willOverride.then(willOverrideHomepage =>
- willOverrideHomepage ? null : uri
- );
+ return uri;
})());
},
@@ -2474,6 +2494,8 @@ var gBrowserInit = {
DownloadsButton.uninit();
+ TorBootstrapUrlbar.uninit();
+
gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) {
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index 8efb544918b8..f16307365728 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -10,6 +10,7 @@
override rules using selectors with the same specificity. This applies to
both "content" and "skin" packages, which bug 1385444 will unify later. -->
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://branding/content/tor-styles.css" type="text/css"?>
<!-- While these stylesheets are defined in Toolkit, they are only used in the
main browser window, so we can load them here. Bug 1474241 is on file to
@@ -110,6 +111,7 @@
Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this);
Services.scriptloader.loadSubScript("chrome://torbutton/content/tor-circuit-display.js", this);
Services.scriptloader.loadSubScript("chrome://torbutton/content/torbutton.js", this);
+ Services.scriptloader.loadSubScript("chrome://browser/content/torconnect/torBootstrapUrlbar.js", this);
window.onload = gBrowserInit.onLoad.bind(gBrowserInit);
window.onunload = gBrowserInit.onUnload.bind(gBrowserInit);
diff --git a/browser/base/content/certerror/aboutNetError.js b/browser/base/content/certerror/aboutNetError.js
index 31c4838a053d..edf97c2a5daf 100644
--- a/browser/base/content/certerror/aboutNetError.js
+++ b/browser/base/content/certerror/aboutNetError.js
@@ -239,7 +239,7 @@ function setErrorPageStrings(err) {
document.l10n.setAttributes(titleElement, title);
}
-function initPage() {
+async function initPage() {
// We show an offline support page in case of a system-wide error,
// when a user cannot connect to the internet and access the SUMO website.
// For example, clock error, which causes certerrors across the web or
@@ -258,6 +258,16 @@ function initPage() {
}
var err = getErrorCode();
+
+ // proxyConnectFailure because no-tor running daemon would return this error
+ if (
+ (err === "proxyConnectFailure") &&
+ (await RPMSendQuery("ShouldShowTorConnect"))
+ ) {
+ // pass orginal destination as redirect param
+ const encodedRedirect = encodeURIComponent(document.location.href);
+ document.location.replace(`about:torconnect?redirect=${encodedRedirect}`);
+ }
// List of error pages with an illustration.
let illustratedErrors = [
"malformedURI",
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 02636a6b46b5..e7f63116ff39 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -330,6 +330,7 @@
data-l10n-id="urlbar-go-button"/>
<hbox id="page-action-buttons" context="pageActionContextMenu">
<toolbartabstop/>
+#include ../../components/torconnect/content/torconnect-urlbar.inc.xhtml
<hbox id="contextual-feature-recommendation" role="button" hidden="true">
<hbox id="cfr-label-container">
<label id="cfr-label"/>
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
index 78eb18a203fd..010e438b33f8 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -20,6 +20,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
ShellService: "resource:///modules/ShellService.jsm",
+ TorConnect: "resource:///modules/TorConnect.jsm",
});
XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
@@ -336,6 +337,19 @@ function openUILinkIn(
aPostData,
aReferrerInfo
) {
+ // make sure users are not faced with the scary red 'tor isn't working' screen
+ // if they navigate to about:tor before bootstrapped
+ //
+ // fixes tor-browser#40752
+ // new tabs also redirect to about:tor if browser.newtabpage.enabled is true
+ // otherwise they go to about:blank
+ if (TorConnect.shouldShowTorConnect) {
+ if (url === "about:tor" ||
+ (url === "about:newtab" && Services.prefs.getBoolPref("browser.newtabpage.enabled", false))) {
+ url = TorConnect.getRedirectURL(url);
+ }
+ }
+
var params;
if (arguments.length == 3 && typeof arguments[2] == "object") {
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 0e0138fe2fd9..fdb8a35f96ac 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -703,6 +703,20 @@ let JSWINDOWACTORS = {
allFrames: true,
},
+ TorConnect: {
+ parent: {
+ moduleURI: "resource:///modules/TorConnectParent.jsm",
+ },
+ child: {
+ moduleURI: "resource:///modules/TorConnectChild.jsm",
+ events: {
+ DOMWindowCreated: {},
+ },
+ },
+
+ matches: ["about:torconnect","about:torconnect?*"],
+ },
+
Translation: {
parent: {
moduleURI: "resource:///modules/translation/TranslationParent.jsm",
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
index 6d283fe67b20..21f673f601d2 100644
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -122,6 +122,10 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::HIDE_FROM_ABOUTABOUT},
{"restartrequired", "chrome://browser/content/aboutRestartRequired.xhtml",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"torconnect", "chrome://browser/content/torconnect/aboutTorConnect.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
};
static nsAutoCString GetAboutModuleName(nsIURI* aURI) {
diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf
index 8ce22e9cff51..733abef1a80f 100644
--- a/browser/components/about/components.conf
+++ b/browser/components/about/components.conf
@@ -26,6 +26,7 @@ pages = [
'robots',
'sessionrestore',
'tabcrashed',
+ 'torconnect',
'welcome',
'welcomeback',
]
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 66de87290bd8..d15ff3051593 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -53,6 +53,7 @@ DIRS += [
"syncedtabs",
"uitour",
"urlbar",
+ "torconnect",
"torpreferences",
"translation",
]
diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm
index 2150c424d8b8..ddeb92378432 100644
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -186,6 +186,10 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/sessionstore/SessionHistory.jsm"
);
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
XPCOMUtils.defineLazyServiceGetters(this, {
gScreenManager: ["@mozilla.org/gfx/screenmanager;1", "nsIScreenManager"],
});
diff --git a/browser/components/torconnect/TorConnectChild.jsm b/browser/components/torconnect/TorConnectChild.jsm
new file mode 100644
index 000000000000..bd6dd549f156
--- /dev/null
+++ b/browser/components/torconnect/TorConnectChild.jsm
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+var EXPORTED_SYMBOLS = ["TorConnectChild"];
+
+const { RemotePageChild } = ChromeUtils.import(
+ "resource://gre/actors/RemotePageChild.jsm"
+);
+
+class TorConnectChild extends RemotePageChild {}
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
new file mode 100644
index 000000000000..2fbc2a5c7c7c
--- /dev/null
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -0,0 +1,147 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+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(
+ "resource:///modules/TorConnect.jsm"
+);
+const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import(
+ "resource:///modules/TorSettings.jsm"
+);
+
+/*
+This object is basically a marshalling interface between the TorConnect module
+and a particular about:torconnect page
+*/
+
+class TorConnectParent extends JSWindowActorParent {
+ constructor(...args) {
+ super(...args);
+
+ const self = this;
+
+ this.state = {
+ State: TorConnect.state,
+ StateChanged: false,
+ ErrorMessage: TorConnect.errorMessage,
+ ErrorDetails: TorConnect.errorDetails,
+ BootstrapProgress: TorConnect.bootstrapProgress,
+ BootstrapStatus: TorConnect.bootstrapStatus,
+ ShowCopyLog: TorConnect.logHasWarningOrError,
+ QuickStartEnabled: TorSettings.quickstart.enabled,
+ };
+
+ // JSWindowActiveParent derived objects cannot observe directly, so create a member
+ // object to do our observing for us
+ //
+ // This object converts the various lifecycle events from the TorConnect module, and
+ // maintains a state object which we pass down to our about:torconnect page, which uses
+ // the state object to update its UI
+ this.torConnectObserver = {
+ observe(aSubject, aTopic, aData) {
+ let obj = aSubject?.wrappedJSObject;
+
+ // update our state struct based on received torconnect topics and forward on
+ // to aboutTorConnect.js
+ self.state.StateChanged = false;
+ 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;
+ self.state.ErrorDetails = null;
+ }
+ break;
+ }
+ case TorConnectTopics.BootstrapProgress: {
+ self.state.BootstrapProgress = obj.progress;
+ self.state.BootstrapStatus = obj.status;
+ self.state.ShowCopyLog = obj.hasWarnings;
+ break;
+ }
+ case TorConnectTopics.BootstrapComplete: {
+ // noop
+ break;
+ }
+ case TorConnectTopics.BootstrapError: {
+ self.state.ErrorMessage = obj.message;
+ self.state.ErrorDetails = obj.details;
+ self.state.ShowCopyLog = true;
+ break;
+ }
+ case TorConnectTopics.FatalError: {
+ // TODO: handle
+ break;
+ }
+ case TorSettingsTopics.SettingChanged:{
+ if (aData === TorSettingsData.QuickStartEnabled) {
+ self.state.QuickStartEnabled = obj.value;
+ } else {
+ // this isn't a setting torconnect cares about
+ return;
+ }
+ break;
+ }
+ default: {
+ console.log(`TorConnect: unhandled observe topic '${aTopic}'`);
+ }
+ }
+
+ self.sendAsyncMessage("torconnect:state-change", self.state);
+ },
+ };
+
+ // observe all of the torconnect:.* topics
+ for (const key in TorConnectTopics) {
+ const topic = TorConnectTopics[key];
+ Services.obs.addObserver(this.torConnectObserver, topic);
+ }
+ Services.obs.addObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged);
+ }
+
+ willDestroy() {
+ // stop observing all of our torconnect:.* topics
+ for (const key in TorConnectTopics) {
+ const topic = TorConnectTopics[key];
+ Services.obs.removeObserver(this.torConnectObserver, topic);
+ }
+ Services.obs.removeObserver(this.torConnectObserver, TorSettingsTopics.SettingChanged);
+ }
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "torconnect:set-quickstart":
+ TorSettings.quickstart.enabled = message.data;
+ TorSettings.saveToPrefs().applySettings();
+ break;
+ case "torconnect:open-tor-preferences":
+ TorConnect.openTorPreferences();
+ break;
+ case "torconnect:copy-tor-logs":
+ return TorConnect.copyTorLogs();
+ case "torconnect:cancel-bootstrap":
+ TorConnect.cancelBootstrap();
+ break;
+ case "torconnect:begin-bootstrap":
+ TorConnect.beginBootstrap();
+ break;
+ case "torconnect:get-init-args":
+ // called on AboutTorConnect.init(), pass down all state data it needs to init
+
+ // pretend this is a state transition on init
+ // so we always get fresh UI
+ this.state.StateChanged = true;
+ return {
+ TorStrings: TorStrings,
+ TorConnectState: TorConnectState,
+ Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
+ State: this.state,
+ };
+ }
+ return undefined;
+ }
+}
diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css
new file mode 100644
index 000000000000..14a3df2a59be
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.css
@@ -0,0 +1,180 @@
+
+/* Copyright (c) 2021, The Tor Project, Inc. */
+
+@import url("chrome://browser/skin/error-pages.css");
+@import url("chrome://branding/content/tor-styles.css");
+
+:root {
+ --onion-opacity: 1;
+ --onion-color: var(--card-outline-color);
+ --onion-radius: 75px;
+}
+
+/* override firefox's default blue focus coloring */
+:focus {
+ outline: none!important;
+ box-shadow: 0 0 0 3px var(--purple-30) !important;
+ border: 1px var(--purple-80) solid !important;
+}
+
+@media (prefers-color-scheme: dark)
+{
+ :focus {
+ box-shadow: 0 0 0 3px var(--purple-50)!important;
+ }
+}
+
+#connectButton {
+ background-color: var(--purple-60)!important;
+ color: white;
+ fill: white;
+}
+
+#connectButton:hover {
+ background-color: var(--purple-70)!important;
+ color: white;
+ fill: white;
+}
+
+#connectButton:active {
+ background-color: var(--purple-80)!important;
+ color: white;
+ fill: white;
+}
+
+/* checkbox css */
+input[type="checkbox"]:not(:disabled) {
+ background-color: var(--grey-20)!important;
+}
+
+input[type="checkbox"]:not(:disabled):checked {
+ background-color: var(--purple-60)!important;
+ color: white;
+ fill: white;
+}
+
+input[type="checkbox"]:not(:disabled):hover {
+ /* override firefox's default blue border on hover */
+ border-color: var(--purple-70);
+ background-color: var(--grey-30)!important;
+}
+
+input[type="checkbox"]:not(:disabled):hover:checked {
+ background-color: var(--purple-70)!important;
+}
+
+input[type="checkbox"]:not(:disabled):active {
+ background-color: var(--grey-40)!important;
+}
+
+input[type="checkbox"]:not(:disabled):active:checked {
+ background-color: var(--purple-80)!important;
+}
+
+#progressBackground {
+ position:fixed;
+ padding:0;
+ margin:0;
+ top:0;
+ left:0;
+ width: 0%;
+ height: 7px;
+ background-image: linear-gradient(90deg, rgb(20, 218, 221) 0%, rgb(128, 109, 236) 100%);
+ border-radius: 0;
+}
+
+#connectPageContainer {
+ margin-top: 10vh;
+ width: 50%;
+}
+
+#quickstartCheckbox, #quickstartCheckboxLabel {
+ vertical-align: middle;
+}
+
+#copyLogButton {
+ position: relative;
+}
+
+/* mirrors p element spacing */
+#copyLogContainer {
+ margin: 1em 0;
+ height: 1.2em;
+ min-height: 1.2em;
+}
+
+#copyLogLink {
+ 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"] {
+ display: none;
+}
+
+#copyLogLink: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;
+ background-color: var(--in-content-page-background);
+}
+
+.title {
+ background-image: url("chrome://browser/content/torconnect/onion.svg");
+ -moz-context-properties: fill, fill-opacity;
+ fill-opacity: var(--onion-opacity);
+ fill: var(--onion-color);
+}
+
+.title.error {
+ background-image: url("chrome://browser/content/torconnect/onion-slash.svg");
+}
+
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js
new file mode 100644
index 000000000000..26b17afb6938
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -0,0 +1,298 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+/* eslint-env mozilla/frame-script */
+
+// populated in AboutTorConnect.init()
+let TorStrings = {};
+let TorConnectState = {};
+
+class AboutTorConnect {
+ selectors = Object.freeze({
+ textContainer: {
+ title: "div.title",
+ titleText: "h1.title-text",
+ },
+ progress: {
+ description: "p#connectShortDescText",
+ meter: "div#progressBackground",
+ },
+ copyLog: {
+ link: "span#copyLogLink",
+ tooltip: "div#copyLogTooltip",
+ tooltipText: "span#copyLogTooltipText",
+ },
+ quickstart: {
+ checkbox: "input#quickstartCheckbox",
+ label: "label#quickstartCheckboxLabel",
+ },
+ buttons: {
+ connect: "button#connectButton",
+ cancel: "button#cancelButton",
+ advanced: "button#advancedButton",
+ },
+ })
+
+ elements = Object.freeze({
+ title: document.querySelector(this.selectors.textContainer.title),
+ titleText: document.querySelector(this.selectors.textContainer.titleText),
+ 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),
+ quickstartLabel: document.querySelector(this.selectors.quickstart.label),
+ connectButton: document.querySelector(this.selectors.buttons.connect),
+ cancelButton: document.querySelector(this.selectors.buttons.cancel),
+ advancedButton: document.querySelector(this.selectors.buttons.advanced),
+ })
+
+ // 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
+
+ beginBootstrap() {
+ this.hide(this.elements.connectButton);
+ this.show(this.elements.cancelButton);
+ this.elements.cancelButton.focus();
+ RPMSendAsyncMessage("torconnect:begin-bootstrap");
+ }
+
+ cancelBootstrap() {
+ RPMSendAsyncMessage("torconnect:cancel-bootstrap");
+ }
+
+ /*
+ Element helper methods
+ */
+
+ show(element) {
+ element.removeAttribute("hidden");
+ }
+
+ hide(element) {
+ element.setAttribute("hidden", "true");
+ }
+
+ setTitle(title, error) {
+ this.elements.titleText.textContent = title;
+ document.title = title;
+
+ if (error) {
+ this.elements.title.classList.add("error");
+ } else {
+ this.elements.title.classList.remove("error");
+ }
+ }
+
+ setProgress(description, visible, percent) {
+ this.elements.progressDescription.textContent = description;
+ if (visible) {
+ this.show(this.elements.progressMeter);
+ this.elements.progressMeter.style.width = `${percent}%`;
+ } else {
+ this.hide(this.elements.progressMeter);
+ }
+ }
+
+ /*
+ 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;
+ }
+
+ /* Per-state updates */
+
+ update_Initial(state) {
+ 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);
+ }
+
+ update_Configuring(state) {
+ const hasError = state.ErrorMessage != null;
+ const showProgressbar = false;
+
+ if (hasError) {
+ this.setTitle(state.ErrorMessage, hasError);
+ this.setProgress(state.ErrorDetails, showProgressbar);
+ this.show(this.elements.copyLogLink);
+ this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+ } 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.show(this.elements.advancedButton);
+ this.hide(this.elements.cancelButton);
+ }
+
+ update_AutoBootstrapping(state) {
+ // TODO: noop until this state is used
+ }
+
+ 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);
+ } else {
+ this.hide(this.elements.copyLogLink);
+ }
+ this.hide(this.elements.connectButton);
+ this.hide(this.elements.advancedButton);
+ this.show(this.elements.cancelButton);
+ if (state.StateChanged) {
+ this.elements.cancelButton.focus();
+ }
+ }
+
+ update_Error(state) {
+ const hasError = true;
+ const showProgressbar = false;
+
+ this.setTitle(state.ErrorMessage, hasError);
+ 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);
+ }
+
+ 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);
+
+ // redirects page to the requested redirect url, removes about:torconnect
+ // from the page stack, so users cannot accidentally go 'back' to the
+ // now unresponsive page
+ window.location.replace(this.redirect);
+ }
+
+ update_Disabled(state) {
+ // TODO: we should probably have some UX here if a user goes to about:torconnect when
+ // it isn't in use (eg using tor-launcher or system tor)
+ }
+
+ async 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);
+ }
+
+ // 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.quickstartCheckbox.addEventListener("change", () => {
+ const quickstart = this.elements.quickstartCheckbox.checked;
+ RPMSendAsyncMessage("torconnect:set-quickstart", quickstart);
+ });
+ this.elements.quickstartLabel.textContent = TorStrings.settings.quickstartCheckbox;
+
+ this.elements.connectButton.textContent =
+ TorStrings.torConnect.torConnectButton;
+ this.elements.connectButton.addEventListener("click", () => {
+ this.beginBootstrap();
+ });
+
+ this.elements.advancedButton.textContent = TorStrings.torConnect.torConfigure;
+ this.elements.advancedButton.addEventListener("click", () => {
+ RPMSendAsyncMessage("torconnect:open-tor-preferences");
+ });
+
+ this.elements.cancelButton.textContent = TorStrings.torConnect.cancel;
+ this.elements.cancelButton.addEventListener("click", () => {
+ this.cancelBootstrap();
+ });
+ }
+
+ initObservers() {
+ // TorConnectParent feeds us state blobs to we use to update our UI
+ RPMAddMessageListener("torconnect:state-change", ({ data }) => {
+ this.updateUI(data);
+ });
+ }
+
+ initKeyboardShortcuts() {
+ 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
+ if (evt.code === "Escape") {
+ this.cancelBootstrap();
+ }
+ };
+ }
+
+ async init() {
+ // see if a user has a final destination after bootstrapping
+ let params = new URLSearchParams(new URL(document.location.href).search);
+ if (params.has("redirect")) {
+ const encodedRedirect = params.get("redirect");
+ this.redirect = decodeURIComponent(encodedRedirect);
+ } else {
+ // if the user gets here manually or via the button in the urlbar
+ // then we will redirect to about:tor
+ this.redirect = "about:tor";
+ }
+
+ let args = await RPMSendQuery("torconnect:get-init-args");
+
+ // various constants
+ TorStrings = Object.freeze(args.TorStrings);
+ TorConnectState = Object.freeze(args.TorConnectState);
+
+ this.initElements(args.Direction);
+ this.initObservers();
+ this.initKeyboardShortcuts();
+
+ // populate UI based on current state
+ this.updateUI(args.State);
+ }
+}
+
+const aboutTorConnect = new AboutTorConnect();
+aboutTorConnect.init();
diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml
new file mode 100644
index 000000000000..595bbdf9a70a
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.xhtml
@@ -0,0 +1,45 @@
+<!-- Copyright (c) 2021, The Tor Project, Inc. -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
+ <link rel="stylesheet" href="chrome://browser/skin/onionPattern.css" type="text/css" media="all" />
+ <link rel="stylesheet" href="chrome://browser/content/torconnect/aboutTorConnect.css" type="text/css" media="all" />
+ </head>
+ <body>
+ <div id="progressBackground"></div>
+ <div id="connectPageContainer" class="container">
+ <div id="text-container">
+ <div class="title">
+ <h1 class="title-text"/>
+ </div>
+ <div id="connectLongContent">
+ <div id="connectShortDesc">
+ <p id="connectShortDescText" />
+ </div>
+ </div>
+
+ <div id="copyLogContainer">
+ <span id="copyLogLink" hidden="true">
+ <div id="copyLogTooltip">
+ <span id="copyLogTooltipText"/>
+ </div>
+ </span>
+ </div>
+
+ <div id="quickstartContainer">
+ <input id="quickstartCheckbox" type="checkbox" />
+ <label id="quickstartCheckboxLabel" for="quickstartCheckbox"/>
+ </div>
+
+ <div id="connectButtonContainer" class="button-container">
+ <button id="advancedButton" hidden="true"></button>
+ <button id="cancelButton" hidden="true"></button>
+ <button id="connectButton" class="primary try-again" hidden="true"></button>
+ </div>
+ </div>
+ </div>
+#include ../../../themes/shared/onionPattern.inc.xhtml
+ </body>
+ <script src="chrome://browser/content/torconnect/aboutTorConnect.js"/>
+</html>
diff --git a/browser/components/torconnect/content/onion-slash.svg b/browser/components/torconnect/content/onion-slash.svg
new file mode 100644
index 000000000000..93eb24b03905
--- /dev/null
+++ b/browser/components/torconnect/content/onion-slash.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="#ff0039" />
+ <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.8613277 C 12.972434,9.9290067 13.25,8.9965102 13.25,8.0000002 c 0,-2.89949 -2.35049,-5.25 -5.25,-5.25 v -1.078125 c 3.4949,0 6.328125,2.833195 6.328125,6.328125 0,1.29533 -0.388841,2.4990528 -1.056641,3.5019528 l 0.841797,0.84375 C 14.986181,11.119703 15.5,9.6196302 15.5,8.0000002 c 0,-4.14214 -3.3579,-7.5 -7.5,-7.5 z m -6.1113281,3.15625 C 1.0154872,4.8821451 0.5,6.3803304 0.5,8.0000002 0.5,12.1421 3.85786,15.5 8,15.5 c 1.6198027,0 3.117896,-0.515441 4.34375,-1.388672 L 11.501953,13.269531 C 10.498
787,13.937828 9.295838,14.328125 8,14.328125 V 13.25 c 0.9967306,0 1.9287093,-0.277621 2.722656,-0.759766 L 9.859375,11.626953 C 9.3016226,11.913918 8.6705338,12.078125 8,12.078125 V 11 C 8.3664751,11 8.716425,10.93088 9.0410156,10.810547 6.6639891,8.4300416 4.2743195,6.0418993 1.8886719,3.6562502 Z" fill-opacity="context-fill-opacity" fill="context-fill"/>
+</svg>
diff --git a/browser/components/torconnect/content/onion.svg b/browser/components/torconnect/content/onion.svg
new file mode 100644
index 000000000000..7655a800d9ee
--- /dev/null
+++ b/browser/components/torconnect/content/onion.svg
@@ -0,0 +1,4 @@
+<?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="M 8 0.5 C 3.85786 0.5 0.5 3.85786 0.5 8 C 0.5 12.1421 3.85786 15.5 8 15.5 C 12.1421 15.5 15.5 12.1421 15.5 8 C 15.5 3.85786 12.1421 0.5 8 0.5 z M 8 1.671875 C 11.4949 1.671875 14.328125 4.50507 14.328125 8 C 14.328125 11.4949 11.4949 14.328125 8 14.328125 L 8 13.25 C 10.89951 13.25 13.25 10.89951 13.25 8 C 13.25 5.10051 10.89951 2.75 8 2.75 L 8 1.671875 z M 8 3.921875 C 10.25231 3.921875 12.078125 5.74772 12.078125 8 C 12.078125 10.25231 10.25231 12.078125 8 12.078125 L 8 11 C 9.65686 11 11 9.65686 11 8 C 11 6.34315 9.65686 5 8 5 L 8 3.921875 z M 8 6.171875 C 9.00965 6.171875 9.828125 6.99036 9.828125 8 C 9.828125 9.00965 9.00965 9.828125 8 9.828125 L 8 6.171875 z " clip-rule="evenodd" fill-rule="evenodd" fill="context-fill" fill-opacity="context-fill-opacity"/>
+</svg>
diff --git a/browser/components/torconnect/content/torBootstrapUrlbar.js b/browser/components/torconnect/content/torBootstrapUrlbar.js
new file mode 100644
index 000000000000..e6a88490f33d
--- /dev/null
+++ b/browser/components/torconnect/content/torBootstrapUrlbar.js
@@ -0,0 +1,93 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
+"use strict";
+
+const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+const { TorStrings } = ChromeUtils.import(
+ "resource:///modules/TorStrings.jsm"
+);
+
+var TorBootstrapUrlbar = {
+ selectors: Object.freeze({
+ torConnect: {
+ box: "hbox#torconnect-box",
+ label: "label#torconnect-label",
+ },
+ }),
+
+ elements: null,
+
+ updateTorConnectBox: function(state) {
+ switch(state)
+ {
+ case TorConnectState.Initial:
+ case TorConnectState.Configuring:
+ case TorConnectState.AutoConfiguring:
+ case TorConnectState.Error:
+ case TorConnectState.FatalError: {
+ this.elements.torConnectBox.removeAttribute("hidden");
+ this.elements.torConnectLabel.textContent =
+ TorStrings.torConnect.torNotConnectedConcise;
+ this.elements.inputContainer.setAttribute("torconnect", "offline");
+ break;
+ }
+ case TorConnectState.Bootstrapping: {
+ this.elements.torConnectBox.removeAttribute("hidden");
+ this.elements.torConnectLabel.textContent =
+ TorStrings.torConnect.torConnectingConcise;
+ this.elements.inputContainer.setAttribute("torconnect", "connecting");
+ break;
+ }
+ case TorConnectState.Bootstrapped: {
+ this.elements.torConnectBox.removeAttribute("hidden");
+ this.elements.torConnectLabel.textContent =
+ TorStrings.torConnect.torConnectedConcise;
+ this.elements.inputContainer.setAttribute("torconnect", "connected");
+ // hide torconnect box after 5 seconds
+ setTimeout(() => {
+ this.elements.torConnectBox.setAttribute("hidden", "true");
+ }, 5000);
+ break;
+ }
+ case TorConnectState.Disabled: {
+ this.elements.torConnectBox.setAttribute("hidden", "true");
+ break;
+ }
+ default:
+ break;
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic === TorConnectTopics.StateChange) {
+ const obj = aSubject?.wrappedJSObject;
+ this.updateTorConnectBox(obj?.state);
+ }
+ },
+
+ init: function() {
+ if (TorConnect.shouldShowTorConnect) {
+ // browser isn't populated until init
+ this.elements = Object.freeze({
+ torConnectBox: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.box),
+ torConnectLabel: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.label),
+ inputContainer: gURLBar._inputContainer,
+ })
+ this.elements.torConnectBox.addEventListener("click", () => {
+ TorConnect.openTorConnect();
+ });
+ Services.obs.addObserver(this, TorConnectTopics.StateChange);
+ this.observing = true;
+ this.updateTorConnectBox(TorConnect.state);
+ }
+ },
+
+ uninit: function() {
+ if (this.observing) {
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ }
+ },
+};
+
diff --git a/browser/components/torconnect/content/torconnect-urlbar.css b/browser/components/torconnect/content/torconnect-urlbar.css
new file mode 100644
index 000000000000..5aabcffedbd0
--- /dev/null
+++ b/browser/components/torconnect/content/torconnect-urlbar.css
@@ -0,0 +1,57 @@
+/*
+ ensure our torconnect button is always visible (same rule as for the bookmark button)
+*/
+hbox.urlbar-page-action#torconnect-box {
+ display: -moz-inline-box!important;
+ height: 28px;
+}
+
+label#torconnect-label {
+ line-height: 28px;
+ margin: 0;
+ opacity: 0.6;
+ padding: 0 0.5em;
+}
+
+/* set appropriate sizes for the non-standard ui densities */
+:root[uidensity=compact] hbox.urlbar-page-action#torconnect-box {
+ height: 24px;
+}
+:root[uidensity=compact] label#torconnect-label {
+ line-height: 24px;
+}
+
+
+:root[uidensity=touch] hbox.urlbar-page-action#torconnect-box {
+ height: 30px;
+}
+:root[uidensity=touch] label#torconnect-label {
+ line-height: 30px;
+}
+
+
+/* hide when hidden attribute is set */
+hbox.urlbar-page-action#torconnect-box[hidden="true"],
+/* hide when user is typing in URL bar */
+#urlbar[usertyping] > #urlbar-input-container > #page-action-buttons > #torconnect-box {
+ display: none!important;
+}
+
+/* hide urlbar's placeholder text when not connectd to tor */
+hbox#urlbar-input-container[torconnect="offline"] input#urlbar-input::placeholder,
+hbox#urlbar-input-container[torconnect="connecting"] input#urlbar-input::placeholder {
+ opacity: 0;
+}
+
+/* hide search suggestions when not connected to tor */
+hbox#urlbar-input-container[torconnect="offline"] + vbox.urlbarView,
+hbox#urlbar-input-container[torconnect="connecting"] + vbox.urlbarView {
+ display: none!important;
+}
+
+/* hide search icon when we are not connected to tor */
+hbox#urlbar-input-container[torconnect="offline"] > #identity-box[pageproxystate="invalid"] > #identity-icon,
+hbox#urlbar-input-container[torconnect="connecting"] > #identity-box[pageproxystate="invalid"] > #identity-icon
+{
+ display: none!important;
+}
diff --git a/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml b/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml
new file mode 100644
index 000000000000..60e985a72691
--- /dev/null
+++ b/browser/components/torconnect/content/torconnect-urlbar.inc.xhtml
@@ -0,0 +1,10 @@
+# Copyright (c) 2021, The Tor Project, Inc.
+
+<hbox id="torconnect-box"
+ class="urlbar-icon-wrapper urlbar-page-action"
+ role="status"
+ hidden="true">
+ <hbox id="torconnect-container">
+ <label id="torconnect-label"/>
+ </hbox>
+</hbox>
\ No newline at end of file
diff --git a/browser/components/torconnect/jar.mn b/browser/components/torconnect/jar.mn
new file mode 100644
index 000000000000..ed8a4de299b2
--- /dev/null
+++ b/browser/components/torconnect/jar.mn
@@ -0,0 +1,7 @@
+browser.jar:
+ content/browser/torconnect/torBootstrapUrlbar.js (content/torBootstrapUrlbar.js)
+ 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/onion.svg (content/onion.svg)
+ content/browser/torconnect/onion-slash.svg (content/onion-slash.svg)
diff --git a/browser/components/torconnect/moz.build b/browser/components/torconnect/moz.build
new file mode 100644
index 000000000000..eb29c31a4243
--- /dev/null
+++ b/browser/components/torconnect/moz.build
@@ -0,0 +1,6 @@
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [
+ 'TorConnectChild.jsm',
+ 'TorConnectParent.jsm',
+]
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index 4e1ccac6bd17..c62edbe9a907 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -10,6 +10,34 @@ const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+
+// in certain scenarios we want user input uris to open in a new tab if they do so from the
+// about:torconnect tab
+function maybeUpdateOpenLocationForTorConnect(openUILinkWhere, currentURI, destinationURI) {
+ try {
+ // only open in new tab if:
+ if (// user is navigating away from about:torconnect
+ currentURI === "about:torconnect" &&
+ // we are trying to open in same tab
+ openUILinkWhere === "current" &&
+ // only if user still has not bootstrapped
+ TorConnect.shouldShowTorConnect &&
+ // and user is not just navigating to about:torconnect
+ destinationURI !== "about:torconnect") {
+ return "tab";
+ }
+ } catch (e) {
+ // swallow exception and fall through returning original so we don't accidentally break
+ // anything if an exception is thrown
+ console.log(e?.message ? e.message : e);
+ }
+
+ return openUILinkWhere;
+};
+
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.jsm",
@@ -2407,6 +2435,10 @@ class UrlbarInput {
this.selectionStart = this.selectionEnd = 0;
}
+ openUILinkWhere = maybeUpdateOpenLocationForTorConnect(
+ openUILinkWhere,
+ this.window.gBrowser.currentURI.asciiSpec,
+ url);
if (openUILinkWhere != "current") {
this.handleRevert();
}
diff --git a/browser/modules/TorProcessService.jsm b/browser/modules/TorProcessService.jsm
new file mode 100644
index 000000000000..201e331b2806
--- /dev/null
+++ b/browser/modules/TorProcessService.jsm
@@ -0,0 +1,12 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorProcessService"];
+
+var TorProcessService = {
+ get isBootstrapDone() {
+ const svc = Cc["@torproject.org/torlauncher-process-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ return svc.mIsBootstrapDone;
+ },
+};
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index 1e08b168e4af..96d3de8186e2 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -257,6 +257,9 @@ var TorStrings = {
"Tor Browser routes your traffic over the Tor Network, run by thousands of volunteers around the world."
),
learnMore: getString("torPreferences.learnMore", "Learn More"),
+ quickstartHeading: getString("torPreferences.quickstart", "Quickstart"),
+ quickstartDescription: getString("torPreferences.quickstartDescription", "Quickstart allows Tor Browser to connect automatically."),
+ quickstartCheckbox : getString("torPreferences.quickstartCheckbox", "Always connect automatically"),
bridgesHeading: getString("torPreferences.bridges", "Bridges"),
bridgesDescription: getString(
"torPreferences.bridgesDescription",
@@ -364,6 +367,83 @@ var TorStrings = {
return retval;
})() /* Tor Network Settings Strings */,
+ torConnect: (() => {
+ const tsbNetwork = new TorDTDStringBundle(
+ ["chrome://torlauncher/locale/network-settings.dtd"],
+ ""
+ );
+ const tsbLauncher = new TorPropertyStringBundle(
+ "chrome://torlauncher/locale/torlauncher.properties",
+ "torlauncher."
+ );
+ const tsbCommon = new TorPropertyStringBundle(
+ "chrome://global/locale/commonDialogs.properties",
+ ""
+ );
+
+ const getStringNet = tsbNetwork.getString.bind(tsbNetwork);
+ const getStringLauncher = tsbLauncher.getString.bind(tsbLauncher);
+ const getStringCommon = tsbCommon.getString.bind(tsbCommon);
+
+ return {
+ torConnect: getStringNet(
+ "torsettings.wizard.title.default",
+ "Connect to Tor"
+ ),
+
+ torConnecting: getStringNet(
+ "torsettings.wizard.title.connecting",
+ "Establishing a Connection"
+ ),
+
+ torNotConnectedConcise: getStringNet(
+ "torConnect.notConnectedConcise",
+ "Not Connected"
+ ),
+
+ torConnectingConcise: getStringNet(
+ "torConnect.connectingConcise",
+ "Connecting…"
+ ),
+
+ torBootstrapFailed: getStringLauncher(
+ "tor_bootstrap_failed",
+ "Tor failed to establish a Tor network connection."
+ ),
+
+ torConfigure: getStringNet(
+ "torsettings.wizard.title.configure",
+ "Tor Network Settings"
+ ),
+
+ copyLog: getStringNet(
+ "torConnect.copyLog",
+ "Copy Tor Logs"
+ ),
+
+ torConnectButton: getStringNet("torSettings.connect", "Connect"),
+
+ cancel: getStringCommon("Cancel", "Cancel"),
+
+ torConnected: getStringLauncher(
+ "torlauncher.bootstrapStatus.done",
+ "Connected to the Tor network"
+ ),
+
+ torConnectedConcise: getStringLauncher(
+ "torConnect.connectedConcise",
+ "Connected"
+ ),
+
+ tryAgain: getStringNet("torConnect.tryAgain", "Try connecting again"),
+ offline: getStringNet("torConnect.offline", "Offline"),
+
+ // tor connect strings for message box in about:preferences#tor
+ 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"),
+ };
+ })(),
+
/*
Tor Onion Services Strings, e.g., for the authentication prompt.
*/
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
index bc543283d887..a06914ccf8d9 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -154,6 +154,8 @@ EXTRA_JS_MODULES += [
"TabsList.jsm",
"TabUnloader.jsm",
"ThemeVariableMap.jsm",
+ 'TorConnect.jsm',
+ 'TorProcessService.jsm',
"TorProtocolService.jsm",
"TorSettings.jsm",
"TorStrings.jsm",
diff --git a/browser/themes/shared/urlbar-searchbar.inc.css b/browser/themes/shared/urlbar-searchbar.inc.css
index 82675dae2041..f91278ce5ed3 100644
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -745,3 +745,6 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
.searchbar-textbox::placeholder {
opacity: 0.69;
}
+
+%include ../../components/torconnect/content/torconnect-urlbar.css
+
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index 0ef4b3236477..1e75ed7fe032 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -17099,9 +17099,56 @@ void Document::RemoveToplevelLoadingDocument(Document* aDoc) {
StylePrefersColorScheme Document::PrefersColorScheme(
IgnoreRFP aIgnoreRFP) const {
+
+ // tor-browser#27476
+ // should this document ignore resist finger-printing settings with regards to
+ // setting the color scheme
+ // currently only enabled for about:torconnect but we could expand to other non-
+ // SystemPrincipal pages if we wish
+ const auto documentUsesPreferredColorScheme = [](auto const* constDocument) -> bool {
+ if (auto* document = const_cast<Document*>(constDocument); document != nullptr) {
+ auto uri = document->GetDocBaseURI();
+
+ // try and extract out our prepath and filepath portions of the uri to C-strings
+ nsAutoCString prePathStr, filePathStr;
+ if(NS_FAILED(uri->GetPrePath(prePathStr)) ||
+ NS_FAILED(uri->GetFilePath(filePathStr))) {
+ return false;
+ }
+
+ // stick them in string view for easy comparisons
+ std::string_view prePath(prePathStr.get(), prePathStr.Length()),
+ filePath(filePathStr.get(), filePathStr.Length());
+
+ // these about URIs will have the user's preferred color scheme exposed to them
+ // we can place other URIs here in the future if we wish
+ // see nsIURI.idl for URI part definitions
+ constexpr struct {
+ std::string_view prePath;
+ std::string_view filePath;
+ } allowedURIs[] = {
+ { "about:", "torconnect" },
+ };
+
+ // check each uri in the allow list against this document's uri
+ // verify the prepath and the file path match
+ for(auto const& uri : allowedURIs) {
+ if (prePath == uri.prePath &&
+ filePath == uri.filePath) {
+ // positive match means we can apply dark-mode to the page
+ return true;
+ }
+ }
+ }
+
+ // do not allow if no match or other error
+ return false;
+ };
+
if (aIgnoreRFP == IgnoreRFP::No &&
- nsContentUtils::ShouldResistFingerprinting(this)) {
- return StylePrefersColorScheme::Light;
+ nsContentUtils::ShouldResistFingerprinting(this) &&
+ !documentUsesPreferredColorScheme(this)) {
+ return StylePrefersColorScheme::Light;
}
if (auto* bc = GetBrowsingContext()) {
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
index 41c93c51cf3b..aab4a37e78a8 100644
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -6212,6 +6212,8 @@ void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) {
NS_ENSURE_SUCCESS_VOID(rv);
if (!StringBeginsWith(url, u"about:neterror"_ns) &&
+ // we want about:torconnect pages to be able to close themselves after bootstrap
+ !StringBeginsWith(url, u"about:torconnect"_ns) &&
!mBrowsingContext->HadOriginalOpener() && !aTrustedCaller &&
!IsOnlyTopLevelDocumentInSHistory()) {
bool allowClose =
diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm
index 7bde782e54ce..ba8cd0f3f97d 100644
--- a/toolkit/components/processsingleton/MainProcessSingleton.jsm
+++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm
@@ -29,6 +29,11 @@ MainProcessSingleton.prototype = {
null
);
+ ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm",
+ null
+ );
+
Services.ppmm.loadProcessScript(
"chrome://global/content/process-content.js",
true
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 50fb4ea8d417..486409ab5c8b 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -102,6 +102,7 @@ let RemotePageAccessManager = {
RPMAddToHistogram: ["*"],
RPMGetInnerMostURI: ["*"],
RPMGetHttpResponseHeader: ["*"],
+ RPMSendQuery: ["ShouldShowTorConnect"],
},
"about:plugins": {
RPMSendQuery: ["RequestPlugins"],
@@ -213,6 +214,21 @@ let RemotePageAccessManager = {
RPMAddMessageListener: ["*"],
RPMRemoveMessageListener: ["*"],
},
+ "about:torconnect": {
+ RPMAddMessageListener: [
+ "torconnect:state-change",
+ ],
+ RPMSendAsyncMessage: [
+ "torconnect:open-tor-preferences",
+ "torconnect:begin-bootstrap",
+ "torconnect:cancel-bootstrap",
+ "torconnect:set-quickstart",
+ ],
+ RPMSendQuery: [
+ "torconnect:get-init-args",
+ "torconnect:copy-tor-logs",
+ ],
+ },
},
/**
diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm
index 4d1b1c59eff5..cd87b21b0ff9 100644
--- a/toolkit/mozapps/update/UpdateService.jsm
+++ b/toolkit/mozapps/update/UpdateService.jsm
@@ -12,6 +12,17 @@ const { AppConstants } = ChromeUtils.import(
const { AUSTLMY } = ChromeUtils.import(
"resource://gre/modules/UpdateTelemetry.jsm"
);
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+function _shouldRegisterBootstrapObserver(errorCode) {
+ return errorCode == PROXY_SERVER_CONNECTION_REFUSED &&
+ !TorProtocolService.isBootstrapDone() &&
+ TorProtocolService.ownsTorDaemon;
+};
+
const {
Bits,
BitsRequest,
@@ -228,6 +239,7 @@ const SERVICE_ERRORS = [
// Custom update error codes
const BACKGROUNDCHECK_MULTIPLE_FAILURES = 110;
const NETWORK_ERROR_OFFLINE = 111;
+const PROXY_SERVER_CONNECTION_REFUSED = 2152398920;
// Error codes should be < 1000. Errors above 1000 represent http status codes
const HTTP_ERROR_OFFSET = 1000;
@@ -2613,6 +2625,9 @@ UpdateService.prototype = {
case "network:offline-status-changed":
this._offlineStatusChanged(data);
break;
+ case "torconnect:bootstrap-complete":
+ this._bootstrapComplete();
+ break;
case "nsPref:changed":
if (data == PREF_APP_UPDATE_LOG || data == PREF_APP_UPDATE_LOG_FILE) {
gLogEnabled; // Assigning this before it is lazy-loaded is an error.
@@ -3063,6 +3078,35 @@ UpdateService.prototype = {
this._attemptResume();
},
+ _registerBootstrapObserver: function AUS__registerBootstrapObserver() {
+ if (this._registeredBootstrapObserver) {
+ LOG(
+ "UpdateService:_registerBootstrapObserver - observer already registered"
+ );
+ return;
+ }
+
+ LOG(
+ "UpdateService:_registerBootstrapObserver - waiting for tor bootstrap to " +
+ "be complete, then forcing another check"
+ );
+
+ Services.obs.addObserver(this, "torconnect:bootstrap-complete");
+ this._registeredBootstrapObserver = true;
+ },
+
+ _bootstrapComplete: function AUS__bootstrapComplete() {
+ Services.obs.removeObserver(this, "torconnect:bootstrap-complete");
+ this._registeredBootstrapObserver = false;
+
+ LOG(
+ "UpdateService:_bootstrapComplete - bootstrapping complete, forcing " +
+ "another background check"
+ );
+
+ this._attemptResume();
+ },
+
onCheckComplete: function AUS_onCheckComplete(request, updates) {
this._selectAndInstallUpdate(updates);
},
@@ -3082,6 +3126,11 @@ UpdateService.prototype = {
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_OFFLINE);
}
return;
+ } else if (_shouldRegisterBootstrapObserver(update.errorCode)) {
+ // Register boostrap observer to try again, but only when we own the
+ // tor process.
+ this._registerBootstrapObserver();
+ return;
}
// Send the error code to telemetry
@@ -5843,6 +5892,7 @@ Downloader.prototype = {
var state = this._patch.state;
var shouldShowPrompt = false;
var shouldRegisterOnlineObserver = false;
+ var shouldRegisterBootstrapObserver = false;
var shouldRetrySoon = false;
var deleteActiveUpdate = false;
let migratedToReadyUpdate = false;
@@ -5961,7 +6011,18 @@ Downloader.prototype = {
);
shouldRegisterOnlineObserver = true;
deleteActiveUpdate = false;
-
+ } else if(_shouldRegisterBootstrapObserver(status)) {
+ // Register a bootstrap observer to try again.
+ // The bootstrap observer will continue the incremental download by
+ // calling downloadUpdate on the active update which continues
+ // downloading the file from where it was.
+ LOG("Downloader:onStopRequest - not bootstrapped, register bootstrap observer: true");
+ AUSTLMY.pingDownloadCode(
+ this.isCompleteUpdate,
+ AUSTLMY.DWNLD_RETRY_OFFLINE
+ );
+ shouldRegisterBootstrapObserver = true;
+ deleteActiveUpdate = false;
// Each of NS_ERROR_NET_TIMEOUT, ERROR_CONNECTION_REFUSED,
// NS_ERROR_NET_RESET and NS_ERROR_DOCUMENT_NOT_CACHED can be returned
// when disconnecting the internet while a download of a MAR is in
@@ -6083,7 +6144,7 @@ Downloader.prototype = {
// Only notify listeners about the stopped state if we
// aren't handling an internal retry.
- if (!shouldRetrySoon && !shouldRegisterOnlineObserver) {
+ if (!shouldRetrySoon && !shouldRegisterOnlineObserver && !shouldRegisterBootstrapObserver) {
this.updateService.forEachDownloadListener(listener => {
listener.onStopRequest(request, status);
});
@@ -6269,6 +6330,9 @@ Downloader.prototype = {
if (shouldRegisterOnlineObserver) {
LOG("Downloader:onStopRequest - Registering online observer");
this.updateService._registerOnlineObserver();
+ } else if (shouldRegisterBootstrapObserver) {
+ LOG("Downloader:onStopRequest - Registering bootstrap observer");
+ this.updateService._registerBootstrapObserver();
} else if (shouldRetrySoon) {
LOG("Downloader:onStopRequest - Retrying soon");
this.updateService._consecutiveSocketErrors++;
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
index 2ff107b553b2..f8fa83574df7 100644
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
@@ -70,6 +70,10 @@ function getGlobalScriptIncludes(scriptPath) {
let match = line.match(globalScriptsRegExp);
if (match) {
let sourceFile = match[1]
+ .replace(
+ "chrome://browser/content/torconnect/",
+ "browser/components/torconnect/content/"
+ )
.replace(
"chrome://browser/content/search/",
"browser/components/search/content/"
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 40562: Added Tor-related preferences to 000-tor-browser.js
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit ee499f37fd2921e70cf227a72c411ba125de872d
Author: Pier Angelo Vendrame <pierov(a)torproject.org>
Date: Tue Jan 18 19:18:48 2022 +0100
Bug 40562: Added Tor-related preferences to 000-tor-browser.js
Before reordering patches, we used to keep the Tor-related patches
(torbutton and tor-launcher) at the beginning.
After that issue, we decided to move them towards the end, however we
kept TB4: Tor Browser's Firefox preference overrides at the beginning
because it influcences many other features.
As a result, to keep bisect working, we split that commit, and moved
all the preferences related to Tor (such as network.proxy.*) here.
---
browser/app/profile/000-tor-browser.js | 69 ++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/browser/app/profile/000-tor-browser.js b/browser/app/profile/000-tor-browser.js
index 10e1d0730bbb..aac1da85e24e 100644
--- a/browser/app/profile/000-tor-browser.js
+++ b/browser/app/profile/000-tor-browser.js
@@ -211,6 +211,17 @@ pref("network.predictor.enabled", false); // Temporarily disabled. See https://b
// Bug 40177: Make sure tracker cookie purging is disabled
pref("privacy.purge_trackers.enabled", false);
+// Proxy and proxy security
+pref("network.proxy.socks", "127.0.0.1");
+pref("network.proxy.socks_port", 9150);
+pref("network.proxy.socks_remote_dns", true);
+pref("network.proxy.no_proxies_on", ""); // For fingerprinting and local service vulns (#10419)
+pref("network.proxy.allow_hijacking_localhost", true); // Allow proxies for localhost (#31065)
+pref("network.proxy.type", 1);
+// Bug 40548: Disable proxy-bypass
+pref("network.proxy.failover_direct", false);
+pref("network.security.ports.banned", "9050,9051,9150,9151");
+pref("network.dns.disabled", true); // This should cover the #5741 patch for DNS leaks
pref("network.dns.disablePrefetch", true);
pref("network.protocol-handler.external-default", false);
pref("network.protocol-handler.external.mailto", false);
@@ -373,6 +384,64 @@ pref("dom.audiochannel.mediaControl", false);
#expand pref("torbrowser.version", __TOR_BROWSER_VERSION_QUOTED__);
+// Old torbutton prefs
+
+// debug prefs
+pref("extensions.torbutton.loglevel",4);
+pref("extensions.torbutton.logmethod",1); // 0=stdout, 1=errorconsole, 2=debuglog
+
+// Display prefs
+pref("extensions.torbutton.display_circuit", true);
+pref("extensions.torbutton(a)torproject.org.description", "chrome://torbutton/locale/torbutton.properties");
+pref("extensions.torbutton.updateNeeded", false);
+
+// Tor check and proxy prefs
+pref("extensions.torbutton.test_enabled",true);
+pref("extensions.torbutton.test_url","https://check.torproject.org/?TorButton=true");
+pref("extensions.torbutton.local_tor_check",true);
+pref("extensions.torbutton.versioncheck_url","https://www.torproject.org/projects/torbrowser/RecommendedTBBVersions");
+pref("extensions.torbutton.versioncheck_enabled",true);
+pref("extensions.torbutton.use_nontor_proxy",false);
+
+// State prefs:
+pref("extensions.torbutton.startup",false);
+pref("extensions.torbutton.inserted_button",false);
+pref("extensions.torbutton.inserted_security_level",false);
+
+// This is only used when letterboxing is disabled.
+// See #7255 for details. We display the warning three times to make sure the
+// user did not click on it by accident.
+pref("extensions.torbutton.maximize_warnings_remaining", 3);
+
+// Security prefs:
+pref("extensions.torbutton.clear_http_auth",true);
+pref("extensions.torbutton.close_newnym",true);
+pref("extensions.torbutton.resize_new_windows",false);
+pref("extensions.torbutton.startup_state", 2); // 0=non-tor, 1=tor, 2=last
+pref("extensions.torbutton.tor_memory_jar",false);
+pref("extensions.torbutton.nontor_memory_jar",false);
+pref("extensions.torbutton.launch_warning",true);
+
+// Opt out of Firefox addon pings:
+// https://developer.mozilla.org/en/Addons/Working_with_AMO
+pref("extensions.torbutton(a)torproject.org.getAddons.cache.enabled", false);
+
+// Security Slider
+pref("extensions.torbutton.security_slider", 4);
+pref("extensions.torbutton.security_custom", false);
+
+pref("extensions.torbutton.confirm_plugins", true);
+pref("extensions.torbutton.confirm_newnym", true);
+
+pref("extensions.torbutton.noscript_inited", false);
+pref("extensions.torbutton.noscript_persist", false);
+
+// Browser home page:
+pref("browser.startup.homepage", "about:tor");
+
+// This pref specifies an ad-hoc "version" for various pref update hacks we need to do
+pref("extensions.torbutton.pref_fixup_version", 0);
+
// If we are bundling fonts, whitelist those bundled fonts, and restrict system fonts to a selection.
#ifdef MOZ_BUNDLED_FONTS
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 12620: TorBrowser regression tests
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 306fad5ef46543fe76d935193281eac572f9a06e
Author: Arthur Edelstein <arthuredelstein(a)gmail.com>
Date: Wed Aug 27 16:25:00 2014 -0700
Bug 12620: TorBrowser regression tests
Regression tests for Bug #2950: Make Permissions Manager memory-only
Regression tests for TB4: Tor Browser's Firefox preference overrides.
Note: many more functional tests could be made here
Regression tests for #2874: Block Components.interfaces from content
Bug 18923: Add a script to run all Tor Browser specific tests
Regression tests for Bug #16441: Suppress "Reset Tor Browser" prompt.
---
run-tbb-tests | 66 +++++++++++++++++++++++++++++++++++
tbb-tests-ignore.txt | 13 +++++++
tbb-tests/browser.ini | 5 +++
tbb-tests/browser_tor_TB4.js | 35 +++++++++++++++++++
tbb-tests/browser_tor_bug2950.js | 74 ++++++++++++++++++++++++++++++++++++++++
tbb-tests/mochitest.ini | 3 ++
tbb-tests/moz.build | 9 +++++
tbb-tests/test_tor_bug2874.html | 25 ++++++++++++++
toolkit/toolkit.mozbuild | 3 +-
9 files changed, 232 insertions(+), 1 deletion(-)
diff --git a/run-tbb-tests b/run-tbb-tests
new file mode 100755
index 000000000000..bc09839f9f05
--- /dev/null
+++ b/run-tbb-tests
@@ -0,0 +1,66 @@
+#!/bin/bash
+
+# This script runs all the Mochitest tests that have been added or
+# modified since the last ffxbld commit.
+#
+# It does not currently run XPCShell tests. We should change this if we
+# start using this type or other types of tests.
+#
+# The logs of the tests are stored in the tbb-tests.log file.
+# Ignored tests are listed in the tbb-tests-ignore.txt file.
+#
+# https://trac.torproject.org/projects/tor/ticket/18923
+
+IFS=$'\n'
+
+if [ -n "$USE_TESTS_LIST" ] && [ -f tbb-tests-list.txt ]
+then
+ echo "Using tests list from file tbb-tests-list.txt"
+ tests=($(cat tbb-tests-list.txt))
+else
+ ffxbld_commit=$(git log -500 --format='oneline' | grep "TB3: Tor Browser's official .mozconfigs." \
+ | head -1 | cut -d ' ' -f 1)
+
+ tests=($(git diff --name-status "$ffxbld_commit" HEAD | \
+ grep -e '^[AM].*/test_[^/]\+\.\(html\|xul\)$' \
+ -e '^[AM].*/browser_[^/]\+\.js$' \
+ | sed 's/^[AM]\s\+//'))
+fi
+
+echo 'The following tests will be run:'
+for i in "${!tests[@]}"
+do
+ if [ -z "$USE_TESTS_LIST" ] \
+ && grep -q "^${tests[$i]}$" tbb-tests-ignore.txt
+ then
+ unset "tests[$i]"
+ continue
+ fi
+ echo "- ${tests[$i]}"
+done
+
+if [ -n "$WRITE_TESTS_LIST" ]
+then
+ rm -f tbb-tests-list.txt
+ for i in "${!tests[@]}"
+ do
+ echo "${tests[$i]}" >> tbb-tests-list.txt
+ done
+ exit 0
+fi
+
+rm -f tbb-tests.log
+echo $'\n''Starting tests'
+# We need `security.nocertdb = false` because of #18087. That pref is
+# forced to have the same value as `browser.privatebrowsing.autostart` in
+# torbutton, so we just set `browser.privatebrowsing.autostart=false` here.
+./mach mochitest --log-tbpl tbb-tests.log \
+ --setpref network.file.path_blacklist='' \
+ --setpref extensions.torbutton.use_nontor_proxy=true \
+ --setpref browser.privatebrowsing.autostart=false \
+ "${tests[@]}"
+
+echo "*************************"
+echo "*************************"
+echo "Summary of failed tests:"
+grep --color=never TEST-UNEXPECTED-FAIL tbb-tests.log
diff --git a/tbb-tests-ignore.txt b/tbb-tests-ignore.txt
new file mode 100644
index 000000000000..ee3927a9e7c4
--- /dev/null
+++ b/tbb-tests-ignore.txt
@@ -0,0 +1,13 @@
+browser/extensions/onboarding/test/browser/browser_onboarding_accessibility.js
+browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js
+browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
+browser/extensions/onboarding/test/browser/browser_onboarding_notification_2.js
+browser/extensions/onboarding/test/browser/browser_onboarding_notification_3.js
+browser/extensions/onboarding/test/browser/browser_onboarding_notification_4.js
+browser/extensions/onboarding/test/browser/browser_onboarding_notification_5.js
+browser/extensions/onboarding/test/browser/browser_onboarding_notification_click_auto_complete_tour.js
+browser/extensions/onboarding/test/browser/browser_onboarding_select_default_tour.js
+browser/extensions/onboarding/test/browser/browser_onboarding_skip_tour.js
+browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js
+browser/extensions/onboarding/test/browser/browser_onboarding_uitour.js
diff --git a/tbb-tests/browser.ini b/tbb-tests/browser.ini
new file mode 100644
index 000000000000..f481660f1417
--- /dev/null
+++ b/tbb-tests/browser.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+
+[browser_tor_bug2950.js]
+[browser_tor_omnibox.js]
+[browser_tor_TB4.js]
diff --git a/tbb-tests/browser_tor_TB4.js b/tbb-tests/browser_tor_TB4.js
new file mode 100644
index 000000000000..8bb12f360e5e
--- /dev/null
+++ b/tbb-tests/browser_tor_TB4.js
@@ -0,0 +1,35 @@
+// # Test for TB4: Tor Browser's Firefox preference overrides
+// This is a minimal test to check whether the 000-tor-browser.js
+// pref overrides are being used at all or not. More comprehensive
+// pref tests are maintained in the tor-browser-bundle-testsuite project.
+
+function test() {
+
+let expectedPrefs = [
+ // Homepage
+ ["browser.startup.homepage", "about:tor"],
+
+ // Disable the "Refresh" prompt that is displayed for stale profiles.
+ ["browser.disableResetPrompt", true],
+
+ // Version placeholder
+ ["torbrowser.version", "dev-build"],
+ ];
+
+let getPref = function (prefName) {
+ let type = Services.prefs.getPrefType(prefName);
+ if (type === Services.prefs.PREF_INT) return Services.prefs.getIntPref(prefName);
+ if (type === Services.prefs.PREF_BOOL) return Services.prefs.getBoolPref(prefName);
+ if (type === Services.prefs.PREF_STRING) return Services.prefs.getCharPref(prefName);
+ // Something went wrong.
+ throw new Error("Can't access pref " + prefName);
+};
+
+let testPref = function([key, expectedValue]) {
+ let foundValue = getPref(key);
+ is(foundValue, expectedValue, "Pref '" + key + "' should be '" + expectedValue +"'.");
+};
+
+expectedPrefs.map(testPref);
+
+} // end function test()
diff --git a/tbb-tests/browser_tor_bug2950.js b/tbb-tests/browser_tor_bug2950.js
new file mode 100644
index 000000000000..16e41344a3c4
--- /dev/null
+++ b/tbb-tests/browser_tor_bug2950.js
@@ -0,0 +1,74 @@
+// # Regression tests for tor Bug #2950, Make Permissions Manager memory-only
+// Ensures that permissions.sqlite file in profile directory is not written to,
+// even when we write a value to Firefox's permissions database.
+
+// The requisite test() function.
+function test() {
+
+// Needed because of asynchronous part later in the test.
+waitForExplicitFinish();
+
+// Shortcut
+let Ci = Components.interfaces;
+
+// ## utility functions
+
+// __principal(spec)__.
+// Creates a principal instance from a spec
+// (string address such as "https://www.torproject.org").
+let principal = spec => Services.scriptSecurityManager.createContentPrincipalFromOrigin(spec);
+
+// __setPermission(spec, key, value)__.
+// Sets the site permission of type key to value, for the site located at address spec.
+let setPermission = (spec, key, value) => SitePermissions.setForPrincipal(principal(spec), key, value);
+
+// __getPermission(spec, key)__.
+// Reads the site permission value for permission type key, for the site
+// located at address spec.
+let getPermission = (spec, key) => SitePermissions.getForPrincipal(principal(spec), key);
+
+// __profileDirPath__.
+// The Firefox Profile directory. Expected location of various persistent files.
+let profileDirPath = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile).path;
+
+// __fileInProfile(fileName)__.
+// Returns an nsIFile instance corresponding to a file in the Profile directory.
+let fileInProfile = fileName => FileUtils.File(profileDirPath + "/" + fileName);
+
+// ## Now let's run the test.
+
+let SITE = "https://www.torproject.org",
+ KEY = "popup";
+
+let permissionsFile = fileInProfile("permissions.sqlite"),
+ lastModifiedTime = null,
+ newModifiedTime = null;
+if (permissionsFile.exists()) {
+ lastModifiedTime = permissionsFile.lastModifiedTime;
+}
+// Read the original value of the permission.
+let originalValue = getPermission(SITE, KEY);
+
+// We need to delay by at least 1000 ms, because that's the granularity
+// of file time stamps, it seems.
+window.setTimeout(
+ function () {
+ // Set the permission to a new value.
+ setPermission(SITE, KEY, SitePermissions.BLOCK);
+ // Now read back the permission value again.
+ let newReadValue = getPermission(SITE, KEY);
+ // Compare to confirm that the permission
+ // value was successfully changed.
+ Assert.notDeepEqual(originalValue, newReadValue, "Set a value in permissions db (perhaps in memory).");
+ // If file existed or now exists, get the current time stamp.
+ if (permissionsFile.exists()) {
+ newModifiedTime = permissionsFile.lastModifiedTime;
+ }
+ // If file was created or modified since we began this test,
+ // then permissions db is not memory only. Complain!
+ is(lastModifiedTime, newModifiedTime, "Don't write to permissions.sqlite file on disk.");
+ // We are done with the test.
+ finish();
+ }, 1100);
+
+} // test()
diff --git a/tbb-tests/mochitest.ini b/tbb-tests/mochitest.ini
new file mode 100644
index 000000000000..cc5172733bbe
--- /dev/null
+++ b/tbb-tests/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_tor_bug2874.html]
diff --git a/tbb-tests/moz.build b/tbb-tests/moz.build
new file mode 100644
index 000000000000..01db60b9c28a
--- /dev/null
+++ b/tbb-tests/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ["mochitest.ini"]
+
+BROWSER_CHROME_MANIFESTS += ["browser.ini"]
diff --git a/tbb-tests/test_tor_bug2874.html b/tbb-tests/test_tor_bug2874.html
new file mode 100644
index 000000000000..c0a956e9f687
--- /dev/null
+++ b/tbb-tests/test_tor_bug2874.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tor bug
+https://trac.torproject.org/projects/tor/ticket/2874
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Tor Bug 2874</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ is(typeof Components, 'undefined', "The global window object should not expose a Components property to untrusted content.");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://trac.torproject.org/projects/tor/ticket/2874">Tor Bug 2874</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild
index 5576f6acc427..a2c3ddf2dc38 100644
--- a/toolkit/toolkit.mozbuild
+++ b/toolkit/toolkit.mozbuild
@@ -99,7 +99,8 @@ if CONFIG['MOZ_WEBRTC'] and CONFIG['COMPILE_ENVIRONMENT']:
]
if CONFIG['ENABLE_TESTS']:
- DIRS += ['/testing/specialpowers']
+ DIRS += ['/testing/specialpowers',
+ '/tbb-tests']
DIRS += [
'/testing/gtest',
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 14631: Improve profile access error messages.
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit a3491df6dee82573e220eaa17b24cf794a9d6a5e
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Tue Feb 24 13:50:23 2015 -0500
Bug 14631: Improve profile access error messages.
Instead of always reporting that the profile is locked, display specific
messages for "access denied" and "read-only file system".
To allow for localization, get profile-related error strings from Torbutton.
Use app display name ("Tor Browser") in profile-related error alerts.
---
.../mozapps/profile/profileSelection.properties | 5 +
toolkit/profile/nsToolkitProfileService.cpp | 57 +++++++-
toolkit/profile/nsToolkitProfileService.h | 13 +-
toolkit/xre/nsAppRunner.cpp | 157 ++++++++++++++++++---
4 files changed, 208 insertions(+), 24 deletions(-)
diff --git a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
index d326083202b2..aa38bda24347 100644
--- a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
@@ -12,6 +12,11 @@ restartMessageUnlocker=%S is already running, but is not responding. The old %S
restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time.
restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.
+# LOCALIZATION NOTE (profileProblemTitle, profileReadOnly, profileReadOnlyMac, profileAccessDenied): Messages displayed when the browser profile cannot be accessed or written to. %S is the application name.
+profileProblemTitle=%S Profile Problem
+profileReadOnly=You cannot run %S from a read-only file system. Please copy %S to another location before trying to use it.
+profileReadOnlyMac=You cannot run %S from a read-only file system. Please copy %S to your Desktop or Applications folder before trying to use it.
+profileAccessDenied=%S does not have permission to access the profile. Please adjust your file system permissions and try again.
# Profile manager
# LOCALIZATION NOTE (profileTooltip): First %S is the profile name, second %S is the path to the profile folder.
profileTooltip=Profile: ‘%S’ — Path: ‘%S’
diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp
index 154806ebbccf..9f8168c07a4f 100644
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -1248,9 +1248,10 @@ nsToolkitProfileService::SelectStartupProfile(
}
bool wasDefault;
+ ProfileStatus profileStatus;
nsresult rv =
SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
- aProfile, aDidCreate, &wasDefault);
+ aProfile, aDidCreate, &wasDefault, profileStatus);
// Since we were called outside of the normal startup path complete any
// startup tasks.
@@ -1283,7 +1284,8 @@ nsToolkitProfileService::SelectStartupProfile(
nsresult nsToolkitProfileService::SelectStartupProfile(
int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
- bool* aWasDefaultSelection) {
+ bool* aWasDefaultSelection, ProfileStatus& aProfileStatus) {
+ aProfileStatus = PROFILE_STATUS_OK;
if (mStartupProfileSelected) {
return NS_ERROR_ALREADY_INITIALIZED;
}
@@ -1376,6 +1378,13 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
NS_ENSURE_SUCCESS(rv, rv);
+ aProfileStatus = CheckProfileWriteAccess(lf);
+ if (PROFILE_STATUS_OK != aProfileStatus) {
+ NS_ADDREF(*aRootDir = lf);
+ NS_ADDREF(*aLocalDir = lf);
+ return NS_ERROR_FAILURE;
+ }
+
// Make sure that the profile path exists and it's a directory.
bool exists;
rv = lf->Exists(&exists);
@@ -2170,3 +2179,47 @@ nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
# error Platform-specific logic needed here.
#endif
}
+
+// Check for write permission to the profile directory by trying to create a
+// new file (after ensuring that no file with the same name exists).
+ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess(
+ nsIFile* aProfileDir) {
+#if defined(XP_UNIX)
+ constexpr auto writeTestFileName = u".parentwritetest"_ns;
+#else
+ constexpr auto writeTestFileName = u"parent.writetest"_ns;
+#endif
+
+ nsCOMPtr<nsIFile> writeTestFile;
+ nsresult rv = aProfileDir->Clone(getter_AddRefs(writeTestFile));
+ if (NS_SUCCEEDED(rv)) rv = writeTestFile->Append(writeTestFileName);
+
+ if (NS_SUCCEEDED(rv)) {
+ bool doesExist = false;
+ rv = writeTestFile->Exists(&doesExist);
+ if (NS_SUCCEEDED(rv) && doesExist) rv = writeTestFile->Remove(true);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = writeTestFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+ (void)writeTestFile->Remove(true);
+ }
+
+ ProfileStatus status =
+ NS_SUCCEEDED(rv) ? PROFILE_STATUS_OK : PROFILE_STATUS_OTHER_ERROR;
+ if (NS_ERROR_FILE_ACCESS_DENIED == rv)
+ status = PROFILE_STATUS_ACCESS_DENIED;
+ else if (NS_ERROR_FILE_READ_ONLY == rv)
+ status = PROFILE_STATUS_READ_ONLY;
+
+ return status;
+}
+
+ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess(
+ nsIToolkitProfile* aProfile) {
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
+ if (NS_FAILED(rv)) return PROFILE_STATUS_OTHER_ERROR;
+
+ return CheckProfileWriteAccess(profileDir);
+}
diff --git a/toolkit/profile/nsToolkitProfileService.h b/toolkit/profile/nsToolkitProfileService.h
index d281d39ebe59..5c97c906df49 100644
--- a/toolkit/profile/nsToolkitProfileService.h
+++ b/toolkit/profile/nsToolkitProfileService.h
@@ -16,6 +16,14 @@
#include "nsProfileLock.h"
#include "nsINIParser.h"
+enum ProfileStatus {
+ PROFILE_STATUS_OK,
+ PROFILE_STATUS_ACCESS_DENIED,
+ PROFILE_STATUS_READ_ONLY,
+ PROFILE_STATUS_IS_LOCKED,
+ PROFILE_STATUS_OTHER_ERROR
+};
+
class nsToolkitProfile final
: public nsIToolkitProfile,
public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> {
@@ -80,10 +88,13 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
nsresult SelectStartupProfile(int* aArgc, char* aArgv[], bool aIsResetting,
nsIFile** aRootDir, nsIFile** aLocalDir,
nsIToolkitProfile** aProfile, bool* aDidCreate,
- bool* aWasDefaultSelection);
+ bool* aWasDefaultSelection,
+ ProfileStatus& aProfileStatus);
nsresult CreateResetProfile(nsIToolkitProfile** aNewProfile);
nsresult ApplyResetProfile(nsIToolkitProfile* aOldProfile);
void CompleteStartup();
+ static ProfileStatus CheckProfileWriteAccess(nsIToolkitProfile* aProfile);
+ static ProfileStatus CheckProfileWriteAccess(nsIFile* aProfileDir);
private:
friend class nsToolkitProfile;
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index d11e586d7096..6d6238feda46 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -2283,6 +2283,91 @@ nsresult LaunchChild(bool aBlankCommandLine, bool aTryExec) {
return NS_ERROR_LAUNCHED_CHILD_PROCESS;
}
+static nsresult GetOverrideStringBundleForLocale(nsIStringBundleService* aSBS,
+ const char* aTorbuttonURI,
+ const char* aLocale,
+ nsIStringBundle** aResult) {
+ NS_ENSURE_ARG(aSBS);
+ NS_ENSURE_ARG(aTorbuttonURI);
+ NS_ENSURE_ARG(aLocale);
+ NS_ENSURE_ARG(aResult);
+
+ const char* kFormatStr =
+ "jar:%s!/chrome/torbutton/locale/%s/torbutton.properties";
+ nsPrintfCString strBundleURL(kFormatStr, aTorbuttonURI, aLocale);
+ nsresult rv = aSBS->CreateBundle(strBundleURL.get(), aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // To ensure that we have a valid string bundle, try to retrieve a string
+ // that we know exists.
+ nsAutoString val;
+ rv = (*aResult)->GetStringFromName("profileProblemTitle", val);
+ if (!NS_SUCCEEDED(rv)) *aResult = nullptr; // No good. Discard it.
+
+ return rv;
+}
+
+static void GetOverrideStringBundle(nsIStringBundleService* aSBS,
+ nsIStringBundle** aResult) {
+ if (!aSBS || !aResult) return;
+
+ *aResult = nullptr;
+
+ // Build Torbutton file URI string by starting from GREDir.
+ RefPtr<nsXREDirProvider> dirProvider = nsXREDirProvider::GetSingleton();
+ if (!dirProvider) return;
+
+ nsCOMPtr<nsIFile> greDir = dirProvider->GetGREDir();
+ if (!greDir) return;
+
+ // Create file URI, extract as string, and append omni.ja relative path.
+ nsCOMPtr<nsIURI> uri;
+ nsAutoCString uriString;
+ if (NS_FAILED(NS_NewFileURI(getter_AddRefs(uri), greDir)) ||
+ NS_FAILED(uri->GetSpec(uriString))) {
+ return;
+ }
+
+ uriString.Append("omni.ja");
+
+ nsAutoCString userAgentLocale;
+ if (!NS_SUCCEEDED(
+ Preferences::GetCString("intl.locale.requested", userAgentLocale))) {
+ return;
+ }
+
+ nsresult rv = GetOverrideStringBundleForLocale(
+ aSBS, uriString.get(), userAgentLocale.get(), aResult);
+ if (NS_FAILED(rv)) {
+ // Try again using base locale, e.g., "en" vs. "en-US".
+ int16_t offset = userAgentLocale.FindChar('-', 1);
+ if (offset > 0) {
+ nsAutoCString shortLocale(Substring(userAgentLocale, 0, offset));
+ rv = GetOverrideStringBundleForLocale(aSBS, uriString.get(),
+ shortLocale.get(), aResult);
+ }
+ }
+}
+
+static nsresult GetFormattedString(nsIStringBundle* aOverrideBundle,
+ nsIStringBundle* aMainBundle,
+ const char* aName,
+ const nsTArray<nsString>& aParams,
+ nsAString& aResult) {
+ NS_ENSURE_ARG(aName);
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aOverrideBundle) {
+ rv = aOverrideBundle->FormatStringFromName(aName, aParams, aResult);
+ }
+
+ // If string was not found in override bundle, use main (browser) bundle.
+ if (NS_FAILED(rv) && aMainBundle)
+ rv = aMainBundle->FormatStringFromName(aName, aParams, aResult);
+
+ return rv;
+}
+
static const char kProfileProperties[] =
"chrome://mozapps/locale/profile/profileSelection.properties";
@@ -2348,7 +2433,7 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE);
- NS_ConvertUTF8toUTF16 appName(gAppData->name);
+ NS_ConvertUTF8toUTF16 appName(MOZ_APP_DISPLAYNAME);
AutoTArray<nsString, 2> params = {appName, appName};
// profileMissing
@@ -2372,11 +2457,12 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
// If aUnlocker is NULL, it is also OK for the following arguments to be NULL:
// aProfileDir, aProfileLocalDir, aResult.
-static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir,
- nsIFile* aProfileLocalDir,
- nsIProfileUnlocker* aUnlocker,
- nsINativeAppSupport* aNative,
- nsIProfileLock** aResult) {
+static ReturnAbortOnError ProfileErrorDialog(nsIFile* aProfileDir,
+ nsIFile* aProfileLocalDir,
+ ProfileStatus aStatus,
+ nsIProfileUnlocker* aUnlocker,
+ nsINativeAppSupport* aNative,
+ nsIProfileLock** aResult) {
nsresult rv;
if (aProfileDir) {
@@ -2406,24 +2492,39 @@ static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir,
sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE);
- NS_ConvertUTF8toUTF16 appName(gAppData->name);
+ nsCOMPtr<nsIStringBundle> overrideSB;
+ GetOverrideStringBundle(sbs, getter_AddRefs(overrideSB));
+
+ NS_ConvertUTF8toUTF16 appName(MOZ_APP_DISPLAYNAME);
AutoTArray<nsString, 3> params = {appName, appName, appName};
nsAutoString killMessage;
#ifndef XP_MACOSX
- rv = sb->FormatStringFromName(
- aUnlocker ? "restartMessageUnlocker" : "restartMessageNoUnlocker2",
- params, killMessage);
+ static const char kRestartUnlocker[] = "restartMessageUnlocker";
+ static const char kRestartNoUnlocker[] = "restartMessageNoUnlocker2";
+ static const char kReadOnly[] = "profileReadOnly";
#else
- rv = sb->FormatStringFromName(
- aUnlocker ? "restartMessageUnlockerMac" : "restartMessageNoUnlockerMac",
- params, killMessage);
-#endif
+ static const char kRestartUnlocker[] = "restartMessageUnlockerMac";
+ static const char kRestartNoUnlocker[] = "restartMessageNoUnlockerMac";
+ static const char kReadOnly[] = "profileReadOnlyMac";
+#endif
+ static const char kAccessDenied[] = "profileAccessDenied";
+
+ const char* errorKey = aUnlocker ? kRestartUnlocker : kRestartNoUnlocker;
+ if (PROFILE_STATUS_READ_ONLY == aStatus)
+ errorKey = kReadOnly;
+ else if (PROFILE_STATUS_ACCESS_DENIED == aStatus)
+ errorKey = kAccessDenied;
+ rv = GetFormattedString(overrideSB, sb, errorKey, params, killMessage);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ const char* titleKey = ((PROFILE_STATUS_READ_ONLY == aStatus) ||
+ (PROFILE_STATUS_ACCESS_DENIED == aStatus))
+ ? "profileProblemTitle"
+ : "restartTitle";
params.SetLength(1);
nsAutoString killTitle;
- rv = sb->FormatStringFromName("restartTitle", params, killTitle);
+ rv = sb->FormatStringFromName(titleKey, params, killTitle);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
#ifdef MOZ_BACKGROUNDTASKS
@@ -2611,6 +2712,13 @@ static nsCOMPtr<nsIToolkitProfile> gResetOldProfile;
static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
nsIFile* aLocalDir, nsIToolkitProfile* aProfile,
nsIProfileLock** aResult) {
+ ProfileStatus status =
+ (aProfile ? nsToolkitProfileService::CheckProfileWriteAccess(aProfile)
+ : nsToolkitProfileService::CheckProfileWriteAccess(aRootDir));
+ if (PROFILE_STATUS_OK != status)
+ return ProfileErrorDialog(aRootDir, aLocalDir, status, nullptr, aNative,
+ aResult);
+
// If you close Firefox and very quickly reopen it, the old Firefox may
// still be closing down. Rather than immediately showing the
// "Firefox is running but is not responding" message, we spend a few
@@ -2637,7 +2745,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
} while (TimeStamp::Now() - start <
TimeDuration::FromSeconds(kLockRetrySeconds));
- return ProfileLockedDialog(aRootDir, aLocalDir, unlocker, aNative, aResult);
+ return ProfileErrorDialog(aRootDir, aLocalDir, PROFILE_STATUS_IS_LOCKED,
+ unlocker, aNative, aResult);
}
// Pick a profile. We need to end up with a profile root dir, local dir and
@@ -2652,7 +2761,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
nsINativeAppSupport* aNative, nsIFile** aRootDir,
nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
- bool* aWasDefaultSelection) {
+ bool* aWasDefaultSelection,
+ nsIProfileLock** aResult) {
StartupTimeline::Record(StartupTimeline::SELECT_PROFILE);
nsresult rv;
@@ -2698,9 +2808,14 @@ static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
// Ask the profile manager to select the profile directories to use.
bool didCreate = false;
- rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset,
- aRootDir, aLocalDir, aProfile,
- &didCreate, aWasDefaultSelection);
+ ProfileStatus profileStatus = PROFILE_STATUS_OK;
+ rv = aProfileSvc->SelectStartupProfile(
+ &gArgc, gArgv, gDoProfileReset, aRootDir, aLocalDir, aProfile, &didCreate,
+ aWasDefaultSelection, profileStatus);
+ if (PROFILE_STATUS_OK != profileStatus) {
+ return ProfileErrorDialog(*aRootDir, *aLocalDir, profileStatus, nullptr,
+ aNative, aResult);
+ }
if (rv == NS_ERROR_SHOW_PROFILE_MANAGER) {
return ShowProfileManager(aProfileSvc, aNative);
@@ -4530,7 +4645,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
nsCOMPtr<nsIToolkitProfile> profile;
rv = SelectProfile(mProfileSvc, mNativeApp, getter_AddRefs(mProfD),
getter_AddRefs(mProfLD), getter_AddRefs(profile),
- &wasDefaultSelection);
+ &wasDefaultSelection, getter_AddRefs(mProfileLock));
if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) {
*aExitFlag = true;
return 0;
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Add TorStrings module for localization
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 52734eee8729aece0d5d07836c8eb8eb0acdcd02
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Jul 24 21:15:20 2020 +0200
Add TorStrings module for localization
---
browser/modules/TorStrings.jsm | 490 +++++++++++++++++++++++++++++++++++++++++
browser/modules/moz.build | 1 +
2 files changed, 491 insertions(+)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
new file mode 100644
index 000000000000..e8a8d37ae373
--- /dev/null
+++ b/browser/modules/TorStrings.jsm
@@ -0,0 +1,490 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorStrings"];
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+);
+const { getLocale } = ChromeUtils.import(
+ "resource://torbutton/modules/utils.js"
+);
+
+XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser"]);
+XPCOMUtils.defineLazyGetter(this, "domParser", () => {
+ const parser = new DOMParser();
+ parser.forceEnableDTD();
+ return parser;
+});
+
+/*
+ Tor DTD String Bundle
+
+ DTD strings loaded from torbutton/tor-launcher, but provide a fallback in case they aren't available
+*/
+class TorDTDStringBundle {
+ constructor(aBundleURLs, aPrefix) {
+ let locations = [];
+ for (const [index, url] of aBundleURLs.entries()) {
+ locations.push(`<!ENTITY % dtd_${index} SYSTEM "${url}">%dtd_${index};`);
+ }
+ this._locations = locations;
+ this._prefix = aPrefix;
+ }
+
+ // copied from testing/marionette/l10n.js
+ localizeEntity(urls, id) {
+ // Use the DOM parser to resolve the entity and extract its real value
+ let header = `<?xml version="1.0"?><!DOCTYPE elem [${this._locations.join(
+ ""
+ )}]>`;
+ let elem = `<elem id="elementID">&${id};</elem>`;
+ let doc = domParser.parseFromString(header + elem, "text/xml");
+ let element = doc.querySelector("elem[id='elementID']");
+
+ if (element === null) {
+ throw new Error(`Entity with id='${id}' hasn't been found`);
+ }
+
+ return element.textContent;
+ }
+
+ getString(key, fallback) {
+ if (key) {
+ try {
+ return this.localizeEntity(this._bundleURLs, `${this._prefix}${key}`);
+ } catch (e) {}
+ }
+
+ // on failure, assign the fallback if it exists
+ if (fallback) {
+ return fallback;
+ }
+ // otherwise return string key
+ return `$(${key})`;
+ }
+}
+
+/*
+ Tor Property String Bundle
+
+ Property strings loaded from torbutton/tor-launcher, but provide a fallback in case they aren't available
+*/
+class TorPropertyStringBundle {
+ constructor(aBundleURL, aPrefix) {
+ try {
+ this._bundle = Services.strings.createBundle(aBundleURL);
+ } catch (e) {}
+
+ this._prefix = aPrefix;
+ }
+
+ getString(key, fallback) {
+ if (key) {
+ try {
+ return this._bundle.GetStringFromName(`${this._prefix}${key}`);
+ } catch (e) {}
+ }
+
+ // on failure, assign the fallback if it exists
+ if (fallback) {
+ return fallback;
+ }
+ // otherwise return string key
+ return `$(${key})`;
+ }
+}
+
+/*
+ Security Level Strings
+*/
+var TorStrings = {
+ /*
+ Tor Browser Security Level Strings
+ */
+ securityLevel: (function() {
+ let tsb = new TorDTDStringBundle(
+ ["chrome://torbutton/locale/torbutton.dtd"],
+ "torbutton.prefs.sec_"
+ );
+ let getString = function(key, fallback) {
+ return tsb.getString(key, fallback);
+ };
+
+ // read localized strings from torbutton; but use hard-coded en-US strings as fallbacks in case of error
+ let retval = {
+ securityLevel: getString("caption", "Security Level"),
+ customWarning: getString("custom_warning", "Custom"),
+ overview: getString(
+ "overview",
+ "Disable certain web features that can be used to attack your security and anonymity."
+ ),
+ standard: {
+ level: getString("standard_label", "Standard"),
+ tooltip: getString("standard_tooltip", "Security Level : Standard"),
+ summary: getString(
+ "standard_description",
+ "All Tor Browser and website features are enabled."
+ ),
+ },
+ safer: {
+ level: getString("safer_label", "Safer"),
+ tooltip: getString("safer_tooltip", "Security Level : Safer"),
+ summary: getString(
+ "safer_description",
+ "Disables website features that are often dangerous, causing some sites to lose functionality."
+ ),
+ description1: getString(
+ "js_on_https_sites_only",
+ "JavaScript is disabled on non-HTTPS sites."
+ ),
+ description2: getString(
+ "limit_typography",
+ "Some fonts and math symbols are disabled."
+ ),
+ description3: getString(
+ "click_to_play_media",
+ "Audio and video (HTML5 media), and WebGL are click-to-play."
+ ),
+ },
+ safest: {
+ level: getString("safest_label", "Safest"),
+ tooltip: getString("safest_tooltip", "Security Level : Safest"),
+ summary: getString(
+ "safest_description",
+ "Only allows website features required for static sites and basic services. These changes affect images, media, and scripts."
+ ),
+ description1: getString(
+ "js_disabled",
+ "JavaScript is disabled by default on all sites."
+ ),
+ description2: getString(
+ "limit_graphics_and_typography",
+ "Some fonts, icons, math symbols, and images are disabled."
+ ),
+ description3: getString(
+ "click_to_play_media",
+ "Audio and video (HTML5 media), and WebGL are click-to-play."
+ ),
+ },
+ custom: {
+ summary: getString(
+ "custom_summary",
+ "Your custom browser preferences have resulted in unusual security settings. For security and privacy reasons, we recommend you choose one of the default security levels."
+ ),
+ },
+ learnMore: getString("learn_more_label", "Learn more"),
+ learnMoreURL: `https://tb-manual.torproject.org/${getLocale()}/security-settings/`,
+ restoreDefaults: getString("restore_defaults", "Restore Defaults"),
+ advancedSecuritySettings: getString(
+ "advanced_security_settings",
+ "Advanced Security Settings\u2026"
+ ),
+ };
+ return retval;
+ })() /* Security Level Strings */,
+
+ /*
+ Tor about:preferences#tor Strings
+ */
+ settings: (function() {
+ let tsb = new TorDTDStringBundle(
+ ["chrome://torlauncher/locale/network-settings.dtd"],
+ ""
+ );
+ let getString = function(key, fallback) {
+ return tsb.getString(key, fallback);
+ };
+
+ let retval = {
+ categoryTitle: getString("torPreferences.categoryTitle", "Tor"),
+ torPreferencesHeading: getString(
+ "torPreferences.torSettings",
+ "Tor Settings"
+ ),
+ 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"),
+ 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(
+ "torsettings.useBridges.bridgeDB",
+ "Request a bridge from torproject.org"
+ ),
+ requestNewBridge: getString(
+ "torPreferences.requestNewBridge",
+ "Request a New Bridge\u2026"
+ ),
+ provideBridge: getString(
+ "torPreferences.provideBridge",
+ "Provide a bridge"
+ ),
+ provideBridgeDirections: getString(
+ "torsettings.useBridges.label",
+ "Enter bridge information from a trusted source."
+ ),
+ provideBridgePlaceholder: getString(
+ "torsettings.useBridges.placeholder",
+ "type address:port (one per line)"
+ ),
+ advancedHeading: getString("torPreferences.advanced", "Advanced"),
+ advancedDescription: getString(
+ "torPreferences.advancedDescription",
+ "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"),
+ proxyTypeSOCKS4: getString("torsettings.useProxy.type.socks4", "SOCKS4"),
+ proxyTypeSOCKS5: getString("torsettings.useProxy.type.socks5", "SOCKS5"),
+ proxyTypeHTTP: getString("torsettings.useProxy.type.http", "HTTP/HTTPS"),
+ proxyAddress: getString("torsettings.useProxy.address", "Address"),
+ proxyAddressPlaceholder: getString(
+ "torsettings.useProxy.address.placeholder",
+ "IP address or hostname"
+ ),
+ proxyPort: getString("torsettings.useProxy.port", "Port"),
+ proxyUsername: getString("torsettings.useProxy.username", "Username"),
+ proxyPassword: getString("torsettings.useProxy.password", "Password"),
+ proxyUsernamePasswordPlaceholder: getString(
+ "torsettings.optional",
+ "Optional"
+ ),
+ useFirewall: getString(
+ "torsettings.firewall.checkbox",
+ "This computer goes through a firewall that only allows connections to certain ports"
+ ),
+ allowedPorts: getString(
+ "torsettings.firewall.allowedPorts",
+ "Allowed Ports"
+ ),
+ allowedPortsPlaceholder: getString(
+ "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"),
+ torLogDialogTitle: getString(
+ "torPreferences.torLogsDialogTitle",
+ "Tor Logs"
+ ),
+ copyLog: getString("torsettings.copyLog", "Copy Tor Log to Clipboard"),
+
+ learnMoreTorBrowserURL: `https://tb-manual.torproject.org/${getLocale()}/about/`,
+ learnMoreBridgesURL: `https://tb-manual.torproject.org/${getLocale()}/bridges/`,
+ learnMoreNetworkSettingsURL: `about:blank`,
+ };
+
+ return retval;
+ })() /* Tor Network Settings Strings */,
+
+ /*
+ Tor Onion Services Strings, e.g., for the authentication prompt.
+ */
+ onionServices: (function() {
+ let tsb = new TorPropertyStringBundle(
+ "chrome://torbutton/locale/torbutton.properties",
+ "onionServices."
+ );
+ let getString = function(key, fallback) {
+ return tsb.getString(key, fallback);
+ };
+
+ const kProblemLoadingSiteFallback = "Problem Loading Onionsite";
+ const kLongDescFallback = "Details: %S";
+
+ let retval = {
+ learnMore: getString("learnMore", "Learn more"),
+ learnMoreURL: `https://support.torproject.org/${getLocale()}/onionservices/client-auth/`,
+ errorPage: {
+ browser: getString("errorPage.browser", "Browser"),
+ network: getString("errorPage.network", "Network"),
+ onionSite: getString("errorPage.onionSite", "Onionsite"),
+ },
+ descNotFound: { // Tor SOCKS error 0xF0
+ pageTitle: getString("descNotFound.pageTitle", kProblemLoadingSiteFallback),
+ header: getString("descNotFound.header", "Onionsite Not Found"),
+ longDescription: getString("descNotFound.longDescription", kLongDescFallback),
+ },
+ descInvalid: { // Tor SOCKS error 0xF1
+ pageTitle: getString("descInvalid.pageTitle", kProblemLoadingSiteFallback),
+ header: getString("descInvalid.header", "Onionsite Cannot Be Reached"),
+ longDescription: getString("descInvalid.longDescription", kLongDescFallback),
+ },
+ introFailed: { // Tor SOCKS error 0xF2
+ pageTitle: getString("introFailed.pageTitle", kProblemLoadingSiteFallback),
+ header: getString("introFailed.header", "Onionsite Has Disconnected"),
+ longDescription: getString("introFailed.longDescription", kLongDescFallback),
+ },
+ rendezvousFailed: { // Tor SOCKS error 0xF3
+ pageTitle: getString("rendezvousFailed.pageTitle", kProblemLoadingSiteFallback),
+ header: getString("rendezvousFailed.header", "Unable to Connect to Onionsite"),
+ longDescription: getString("rendezvousFailed.longDescription", kLongDescFallback),
+ },
+ clientAuthMissing: { // Tor SOCKS error 0xF4
+ pageTitle: getString("clientAuthMissing.pageTitle", "Authorization Required"),
+ header: getString("clientAuthMissing.header", "Onionsite Requires Authentication"),
+ longDescription: getString("clientAuthMissing.longDescription", kLongDescFallback),
+ },
+ clientAuthIncorrect: { // Tor SOCKS error 0xF5
+ pageTitle: getString("clientAuthIncorrect.pageTitle", "Authorization Failed"),
+ header: getString("clientAuthIncorrect.header", "Onionsite Authentication Failed"),
+ longDescription: getString("clientAuthIncorrect.longDescription", kLongDescFallback),
+ },
+ badAddress: { // Tor SOCKS error 0xF6
+ pageTitle: getString("badAddress.pageTitle", kProblemLoadingSiteFallback),
+ header: getString("badAddress.header", "Invalid Onionsite Address"),
+ longDescription: getString("badAddress.longDescription", kLongDescFallback),
+ },
+ introTimedOut: { // Tor SOCKS error 0xF7
+ pageTitle: getString("introTimedOut.pageTitle", kProblemLoadingSiteFallback),
+ header: getString("introTimedOut.header", "Onionsite Circuit Creation Timed Out"),
+ longDescription: getString("introTimedOut.longDescription", kLongDescFallback),
+ },
+ authPrompt: {
+ description:
+ getString("authPrompt.description2", "%S is requesting that you authenticate."),
+ keyPlaceholder: getString("authPrompt.keyPlaceholder", "Enter your key"),
+ done: getString("authPrompt.done", "Done"),
+ doneAccessKey: getString("authPrompt.doneAccessKey", "d"),
+ invalidKey: getString("authPrompt.invalidKey", "Invalid key"),
+ failedToSetKey:
+ getString("authPrompt.failedToSetKey", "Failed to set key"),
+ },
+ authPreferences: {
+ header: getString("authPreferences.header", "Onion Services Authentication"),
+ overview: getString("authPreferences.overview", "Some onion services require that you identify yourself with a key"),
+ savedKeys: getString("authPreferences.savedKeys", "Saved Keys"),
+ dialogTitle: getString("authPreferences.dialogTitle", "Onion Services Keys"),
+ dialogIntro: getString("authPreferences.dialogIntro", "Keys for the following onionsites are stored on your computer"),
+ onionSite: getString("authPreferences.onionSite", "Onionsite"),
+ onionKey: getString("authPreferences.onionKey", "Key"),
+ remove: getString("authPreferences.remove", "Remove"),
+ removeAll: getString("authPreferences.removeAll", "Remove All"),
+ failedToGetKeys: getString("authPreferences.failedToGetKeys", "Failed to get keys"),
+ failedToRemoveKey: getString("authPreferences.failedToRemoveKey", "Failed to remove key"),
+ },
+ };
+
+ return retval;
+ })() /* Tor Onion Services Strings */,
+
+ /*
+ OnionLocation
+ */
+ onionLocation: (function() {
+ const tsb = new TorPropertyStringBundle(
+ ["chrome://torbutton/locale/torbutton.properties"],
+ "onionLocation."
+ );
+ const getString = function(key, fallback) {
+ return tsb.getString(key, fallback);
+ };
+
+ const retval = {
+ alwaysPrioritize: getString(
+ "alwaysPrioritize",
+ "Always Prioritize Onionsites"
+ ),
+ alwaysPrioritizeAccessKey: getString("alwaysPrioritizeAccessKey", "a"),
+ notNow: getString("notNow", "Not Now"),
+ notNowAccessKey: getString("notNowAccessKey", "n"),
+ description: getString(
+ "description",
+ "Website publishers can protect users by adding a security layer. This prevents eavesdroppers from knowing that you are the one visiting that website."
+ ),
+ tryThis: getString("tryThis", "Try this: Onionsite"),
+ onionAvailable: getString("onionAvailable", "Onionsite available"),
+ learnMore: getString("learnMore", "Learn more"),
+ learnMoreURL: `https://tb-manual.torproject.org/${getLocale()}/onion-services/`,
+ always: getString("always", "Always"),
+ askEverytime: getString("askEverytime", "Ask you every time"),
+ prioritizeOnionsDescription: getString(
+ "prioritizeOnionsDescription",
+ "Prioritize onionsites when they are available."
+ ),
+ onionServicesTitle: getString("onionServicesTitle", "Onion Services"),
+ };
+
+ return retval;
+ })() /* OnionLocation */,
+
+ /*
+ Tor Deamon Configuration Key Strings
+ */
+
+ // TODO: proper camel case
+ configKeys: {
+ /* Bridge Conf Settings */
+ useBridges: "UseBridges",
+ bridgeList: "Bridge",
+ /* Proxy Conf Strings */
+ socks4Proxy: "Socks4Proxy",
+ socks5Proxy: "Socks5Proxy",
+ socks5ProxyUsername: "Socks5ProxyUsername",
+ socks5ProxyPassword: "Socks5ProxyPassword",
+ httpsProxy: "HTTPSProxy",
+ httpsProxyAuthenticator: "HTTPSProxyAuthenticator",
+ /* Firewall Conf Strings */
+ reachableAddresses: "ReachableAddresses",
+
+ /* BridgeDB Strings */
+ clientTransportPlugin: "ClientTransportPlugin",
+ },
+
+ /*
+ about:config preference keys
+ */
+
+ preferenceKeys: {
+ defaultBridgeType: "extensions.torlauncher.default_bridge_type",
+ recommendedBridgeType:
+ "extensions.torlauncher.default_bridge_recommended_type",
+ },
+
+ /*
+ about:config preference branches
+ */
+ preferenceBranches: {
+ defaultBridge: "extensions.torlauncher.default_bridge.",
+ bridgeDBBridges: "extensions.torlauncher.bridgedb_bridge.",
+ },
+};
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
index 646784690c9a..bc543283d887 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -156,6 +156,7 @@ EXTRA_JS_MODULES += [
"ThemeVariableMap.jsm",
"TorProtocolService.jsm",
"TorSettings.jsm",
+ "TorStrings.jsm",
"TransientPrefs.jsm",
"webrtcUI.jsm",
"ZoomUI.jsm",
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 19273: Avoid JavaScript patching of the external app helper dialog.
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit edf723fff50af1f6d072f0def2e92fb605cbbffb
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Tue Jun 28 15:13:05 2016 -0400
Bug 19273: Avoid JavaScript patching of the external app helper dialog.
When handling an external URI or downloading a file, invoke Torbutton's
external app blocker component (which will present a download warning
dialog unless the user has checked the "Automatically download files
from now on" box).
For e10s compatibility, avoid using a modal dialog and instead use
a callback interface (nsIHelperAppWarningLauncher) to allow Torbutton
to indicate the user's desire to cancel or continue each request.
Other bugs fixed:
Bug 21766: Crash with e10s enabled while trying to download a file
Bug 21886: Download is stalled in non-e10s mode
Bug 22471: Downloading files via the PDF viewer download button is broken
Bug 22472: Fix FTP downloads when external helper app dialog is shown
Bug 22610: Avoid crashes when canceling external helper app downloads
Bug 22618: Downloading pdf file via file:/// is stalling
---
.../exthandler/nsExternalHelperAppService.cpp | 177 ++++++++++++++++++---
uriloader/exthandler/nsExternalHelperAppService.h | 3 +
.../exthandler/nsIExternalHelperAppService.idl | 47 ++++++
3 files changed, 209 insertions(+), 18 deletions(-)
diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp
index 0d697b3bec33..a34e7d983cf0 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -133,6 +133,9 @@ static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
"browser.helperApps.neverAsk.openFile";
+static const char WARNING_DIALOG_CONTRACT_ID[] =
+ "@torproject.org/torbutton-extAppBlocker;1";
+
// Helper functions for Content-Disposition headers
/**
@@ -423,6 +426,22 @@ static nsresult GetDownloadDirectory(nsIFile** _directory,
return NS_OK;
}
+static already_AddRefed<nsIInterfaceRequestor> GetDialogParentAux(
+ BrowsingContext* aBrowsingContext, nsIInterfaceRequestor* aWindowContext) {
+ nsCOMPtr<nsIInterfaceRequestor> dialogParent = aWindowContext;
+
+ if (!dialogParent && aBrowsingContext) {
+ dialogParent = do_QueryInterface(aBrowsingContext->GetDOMWindow());
+ }
+ if (!dialogParent && aBrowsingContext && XRE_IsParentProcess()) {
+ RefPtr<Element> element = aBrowsingContext->Top()->GetEmbedderElement();
+ if (element) {
+ dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
+ }
+ }
+ return dialogParent.forget();
+}
+
/**
* Structure for storing extension->type mappings.
* @see defaultMimeEntries
@@ -627,6 +646,96 @@ static const char* descriptionOverwriteExtensions[] = {
"avif", "jxl", "pdf", "svg", "webp", "xml",
};
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// begin nsExternalLoadURIHandler class definition and implementation
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+class nsExternalLoadURIHandler final : public nsIHelperAppWarningLauncher {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIHELPERAPPWARNINGLAUNCHER
+
+ nsExternalLoadURIHandler(nsIHandlerInfo* aHandlerInfo, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal,
+ BrowsingContext* aBrowsingContext,
+ bool aTriggeredExternally);
+
+ protected:
+ ~nsExternalLoadURIHandler();
+
+ nsCOMPtr<nsIHandlerInfo> mHandlerInfo;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ RefPtr<BrowsingContext> mBrowsingContext;
+ bool mTriggeredExternally;
+ nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog;
+};
+
+NS_IMPL_ADDREF(nsExternalLoadURIHandler)
+NS_IMPL_RELEASE(nsExternalLoadURIHandler)
+
+NS_INTERFACE_MAP_BEGIN(nsExternalLoadURIHandler)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHelperAppWarningLauncher)
+ NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher)
+NS_INTERFACE_MAP_END
+
+nsExternalLoadURIHandler::nsExternalLoadURIHandler(
+ nsIHandlerInfo* aHandlerInfo, nsIURI* aURI,
+ nsIPrincipal* aTriggeringPrincipal, BrowsingContext* aBrowsingContext,
+ bool aTriggeredExternally)
+ : mHandlerInfo(aHandlerInfo),
+ mURI(aURI),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mBrowsingContext(aBrowsingContext),
+ mTriggeredExternally(aTriggeredExternally)
+
+{
+ nsresult rv = NS_OK;
+ mWarningDialog = do_CreateInstance(WARNING_DIALOG_CONTRACT_ID, &rv);
+ if (NS_SUCCEEDED(rv) && mWarningDialog) {
+ // This will create a reference cycle (the dialog holds a reference to us
+ // as nsIHelperAppWarningLauncher), which will be broken in ContinueRequest
+ // or CancelRequest.
+ nsCOMPtr<nsIInterfaceRequestor> dialogParent =
+ GetDialogParentAux(aBrowsingContext, nullptr);
+ rv = mWarningDialog->MaybeShow(this, dialogParent);
+ }
+
+ if (NS_FAILED(rv)) {
+ // If for some reason we could not open the download warning prompt,
+ // continue with the request.
+ ContinueRequest();
+ }
+}
+
+nsExternalLoadURIHandler::~nsExternalLoadURIHandler() {}
+
+NS_IMETHODIMP nsExternalLoadURIHandler::ContinueRequest() {
+ MOZ_ASSERT(mURI);
+ MOZ_ASSERT(mHandlerInfo);
+
+ // Break our reference cycle with the download warning dialog (set up in
+ // LoadURI).
+ mWarningDialog = nullptr;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIContentDispatchChooser> chooser =
+ do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return chooser->HandleURI(mHandlerInfo, mURI, mTriggeringPrincipal,
+ mBrowsingContext, mTriggeredExternally);
+}
+
+NS_IMETHODIMP nsExternalLoadURIHandler::CancelRequest(nsresult aReason) {
+ NS_ENSURE_ARG(NS_FAILED(aReason));
+
+ // Break our reference cycle with the download warning dialog (set up in
+ // LoadURI).
+ mWarningDialog = nullptr;
+
+ return NS_OK;
+}
+
static StaticRefPtr<nsExternalHelperAppService> sExtHelperAppSvcSingleton;
/**
@@ -653,6 +762,9 @@ nsExternalHelperAppService::GetSingleton() {
return do_AddRef(sExtHelperAppSvcSingleton);
}
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// nsExternalHelperAppService definition and implementation
+//////////////////////////////////////////////////////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(nsExternalHelperAppService, nsIExternalHelperAppService,
nsPIExternalAppLauncher, nsIExternalProtocolService,
nsIMIMEService, nsIObserver, nsISupportsWeakReference)
@@ -1143,14 +1255,15 @@ nsExternalHelperAppService::LoadURI(nsIURI* aURI,
rv = GetProtocolHandlerInfo(scheme, getter_AddRefs(handler));
NS_ENSURE_SUCCESS(rv, rv);
- nsCOMPtr<nsIContentDispatchChooser> chooser =
- do_CreateInstance("@mozilla.org/content-dispatch-chooser;1", &rv);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return chooser->HandleURI(
+ RefPtr<nsExternalLoadURIHandler> h = new nsExternalLoadURIHandler(
handler, escapedURI,
aRedirectPrincipal ? aRedirectPrincipal : aTriggeringPrincipal,
aBrowsingContext, aTriggeredExternally);
+ if (!h) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1295,6 +1408,7 @@ NS_INTERFACE_MAP_BEGIN(nsExternalAppHandler)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIHelperAppLauncher)
+ NS_INTERFACE_MAP_ENTRY(nsIHelperAppWarningLauncher)
NS_INTERFACE_MAP_ENTRY(nsICancelable)
NS_INTERFACE_MAP_ENTRY(nsIBackgroundFileSaverObserver)
NS_INTERFACE_MAP_ENTRY(nsINamed)
@@ -1693,18 +1807,7 @@ void nsExternalAppHandler::MaybeApplyDecodingForExtension(
already_AddRefed<nsIInterfaceRequestor>
nsExternalAppHandler::GetDialogParent() {
- nsCOMPtr<nsIInterfaceRequestor> dialogParent = mWindowContext;
-
- if (!dialogParent && mBrowsingContext) {
- dialogParent = do_QueryInterface(mBrowsingContext->GetDOMWindow());
- }
- if (!dialogParent && mBrowsingContext && XRE_IsParentProcess()) {
- RefPtr<Element> element = mBrowsingContext->Top()->GetEmbedderElement();
- if (element) {
- dialogParent = do_QueryInterface(element->OwnerDoc()->GetWindow());
- }
- }
- return dialogParent.forget();
+ return GetDialogParentAux(mBrowsingContext, mWindowContext);
}
NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
@@ -1832,6 +1935,34 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
loadInfo->SetForceAllowDataURI(true);
}
+ mWarningDialog = do_CreateInstance(WARNING_DIALOG_CONTRACT_ID, &rv);
+ if (NS_SUCCEEDED(rv) && mWarningDialog) {
+ // This will create a reference cycle (the dialog holds a reference to us
+ // as nsIHelperAppWarningLauncher), which will be broken in ContinueRequest
+ // or CancelRequest.
+ nsCOMPtr<nsIInterfaceRequestor> dialogParent = GetDialogParent();
+ rv = mWarningDialog->MaybeShow(this, dialogParent);
+ }
+
+ if (NS_FAILED(rv)) {
+ // If for some reason we could not open the download warning prompt,
+ // continue with the request.
+ ContinueRequest();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsExternalAppHandler::ContinueRequest() {
+ nsAutoCString MIMEType;
+ if (mMimeInfo) {
+ mMimeInfo->GetMIMEType(MIMEType);
+ }
+
+ // Break our reference cycle with the download warning dialog (set up in
+ // OnStartRequest).
+ mWarningDialog = nullptr;
+
// now that the temp file is set up, find out if we need to invoke a dialog
// asking the user what they want us to do with this content...
@@ -1943,6 +2074,8 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
action == nsIMIMEInfo::saveToDisk) {
alwaysAsk = true;
}
+
+ nsresult rv = NS_OK;
if (alwaysAsk) {
// Display the dialog
mDialog = do_CreateInstance(NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv);
@@ -2000,6 +2133,14 @@ NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest* request) {
return NS_OK;
}
+NS_IMETHODIMP nsExternalAppHandler::CancelRequest(nsresult aReason) {
+ // Break our reference cycle with the download warning dialog (set up in
+ // OnStartRequest).
+ mWarningDialog = nullptr;
+
+ return Cancel(aReason);
+}
+
// Convert error info into proper message text and send OnStatusChange
// notification to the dialog progress listener or nsITransfer implementation.
void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv,
@@ -2686,7 +2827,7 @@ NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason) {
}
// Break our reference cycle with the helper app dialog (set up in
- // OnStartRequest)
+ // ContinueRequest)
mDialog = nullptr;
mRequest = nullptr;
diff --git a/uriloader/exthandler/nsExternalHelperAppService.h b/uriloader/exthandler/nsExternalHelperAppService.h
index ff933451acbd..39f00efb644b 100644
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -224,6 +224,7 @@ class nsExternalHelperAppService : public nsIExternalHelperAppService,
*/
class nsExternalAppHandler final : public nsIStreamListener,
public nsIHelperAppLauncher,
+ public nsIHelperAppWarningLauncher,
public nsIBackgroundFileSaverObserver,
public nsINamed {
public:
@@ -231,6 +232,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSIHELPERAPPLAUNCHER
+ NS_DECL_NSIHELPERAPPWARNINGLAUNCHER
NS_DECL_NSICANCELABLE
NS_DECL_NSIBACKGROUNDFILESAVEROBSERVER
NS_DECL_NSINAMED
@@ -502,6 +504,7 @@ class nsExternalAppHandler final : public nsIStreamListener,
nsCOMPtr<nsITransfer> mTransfer;
nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
+ nsCOMPtr<nsIHelperAppWarningDialog> mWarningDialog;
/**
diff --git a/uriloader/exthandler/nsIExternalHelperAppService.idl b/uriloader/exthandler/nsIExternalHelperAppService.idl
index 657e15bc0742..ebdb1cdacf78 100644
--- a/uriloader/exthandler/nsIExternalHelperAppService.idl
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -177,3 +177,50 @@ interface nsIHelperAppLauncher : nsICancelable
*/
readonly attribute uint64_t browsingContextId;
};
+
+/**
+ * nsIHelperAppWarningLauncher is implemented by two classes:
+ * nsExternalLoadURIHandler
+ * nsExternalAppHandler
+ */
+[scriptable, uuid(cffd508b-4aaf-43ad-99c6-671d35cbc558)]
+interface nsIHelperAppWarningLauncher : nsISupports
+{
+ /**
+ * Callback invoked by the external app warning dialog to continue the
+ * request.
+ * NOTE: This will release the reference to the nsIHelperAppWarningDialog.
+ */
+ void continueRequest();
+
+ /**
+ * Callback invoked by the external app warning dialog to cancel the request.
+ * NOTE: This will release the reference to the nsIHelperAppWarningDialog.
+ *
+ * @param aReason
+ * Pass a failure code to indicate the reason why this operation is
+ * being canceled. It is an error to pass a success code.
+ */
+ void cancelRequest(in nsresult aReason);
+};
+
+/**
+ * nsIHelperAppWarningDialog is implemented by Torbutton's external app
+ * blocker (src/components/external-app-blocker.js).
+ */
+[scriptable, uuid(f4899a3f-0df3-42cc-9db8-bdf599e5a208)]
+interface nsIHelperAppWarningDialog : nsISupports
+{
+ /**
+ * Possibly show a launch warning dialog (it will not be shown if the user
+ * has chosen to not see the warning again).
+ *
+ * @param aLauncher
+ * A nsIHelperAppWarningLauncher to be invoked after the user confirms
+ * or cancels the download.
+ * @param aWindowContext
+ * The window associated with the download.
+ */
+ void maybeShow(in nsIHelperAppWarningLauncher aLauncher,
+ in nsISupports aWindowContext);
+};
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 97e875c43b0b508acaea5163090c39ac743bd91c
Author: Amogh Pradeep <amoghbl1(a)gmail.com>
Date: Fri Jun 12 02:07:45 2015 -0400
Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
See Bug 1357997 for partial uplift.
Also:
Bug 28051 - Use our Orbot for proxying our connections
Bug 31144 - ESR68 Network Code Review
---
.../java/org/mozilla/gecko/util/ProxySelector.java | 25 +++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
index dbd07a069de1..800c7cf96de8 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
@@ -29,6 +29,10 @@ import java.net.URLConnection;
import java.util.List;
public class ProxySelector {
+ private static final String TOR_PROXY_ADDRESS = "127.0.0.1";
+ private static final int TOR_SOCKS_PROXY_PORT = 9150;
+ private static final int TOR_HTTP_PROXY_PORT = 8218;
+
public static URLConnection openConnectionWithProxy(final URI uri) throws IOException {
final java.net.ProxySelector ps = java.net.ProxySelector.getDefault();
Proxy proxy = Proxy.NO_PROXY;
@@ -39,7 +43,26 @@ public class ProxySelector {
}
}
- return uri.toURL().openConnection(proxy);
+ /* Ignore the proxy we found from the VM, only use Tor. We can probably
+ * safely use the logic in this class in the future. */
+ return uri.toURL().openConnection(getProxy());
+ }
+
+ public static Proxy getProxy() {
+ // TODO make configurable
+ return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TOR_PROXY_ADDRESS, TOR_SOCKS_PROXY_PORT));
+ }
+
+ public static String getProxyHostAddress() {
+ return TOR_PROXY_ADDRESS;
+ }
+
+ public static int getSocksProxyPort() {
+ return TOR_SOCKS_PROXY_PORT;
+ }
+
+ public static int getHttpProxyPort() {
+ return TOR_HTTP_PROXY_PORT;
}
public ProxySelector() {
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#tor
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit e51211f5380a30a70236c9d734e666ab3b63bb9e
Author: Richard Pospesel <richard(a)torproject.org>
Date: Mon Sep 16 15:25:39 2019 -0700
Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#tor
This patch adds a new about:preferences#tor page which allows modifying
bridge, proxy, and firewall settings from within Tor Browser. All of the
functionality present in tor-launcher's Network Configuration panel is
present:
- Setting built-in bridges
- Requesting bridges from BridgeDB via moat
- Using user-provided bridges
- Configuring SOCKS4, SOCKS5, and HTTP/HTTPS proxies
- Setting firewall ports
- Viewing and Copying Tor's logs
- The Networking Settings in General preferences has been removed
---
browser/components/moz.build | 1 +
browser/components/preferences/main.inc.xhtml | 54 --
browser/components/preferences/main.js | 14 -
browser/components/preferences/preferences.js | 9 +
browser/components/preferences/preferences.xhtml | 5 +
.../torpreferences/content/requestBridgeDialog.jsm | 209 +++++
.../content/requestBridgeDialog.xhtml | 35 +
.../torpreferences/content/torCategory.inc.xhtml | 9 +
.../torpreferences/content/torLogDialog.jsm | 66 ++
.../torpreferences/content/torLogDialog.xhtml | 23 +
.../components/torpreferences/content/torPane.js | 940 +++++++++++++++++++++
.../torpreferences/content/torPane.xhtml | 157 ++++
.../torpreferences/content/torPreferences.css | 189 +++++
.../torpreferences/content/torPreferencesIcon.svg | 8 +
browser/components/torpreferences/jar.mn | 10 +
browser/components/torpreferences/moz.build | 1 +
16 files changed, 1662 insertions(+), 68 deletions(-)
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 1bc09f4093fb..66de87290bd8 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -53,6 +53,7 @@ DIRS += [
"syncedtabs",
"uitour",
"urlbar",
+ "torpreferences",
"translation",
]
diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml
index a89b89f723a8..594711e61474 100644
--- a/browser/components/preferences/main.inc.xhtml
+++ b/browser/components/preferences/main.inc.xhtml
@@ -671,58 +671,4 @@
<label id="cfrFeaturesLearnMore" class="learnMore" data-l10n-id="browsing-cfr-recommendations-learn-more" is="text-link"/>
</hbox>
</groupbox>
-
-<hbox id="networkProxyCategory"
- class="subcategory"
- hidden="true"
- data-category="paneGeneral">
- <html:h1 data-l10n-id="network-settings-title"/>
-</hbox>
-
-<!-- Network Settings-->
-<groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
- <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label>
-
- <hbox align="center">
- <hbox align="center" flex="1">
- <description id="connectionSettingsDescription" control="connectionSettings"/>
- <spacer width="5"/>
- <label id="connectionSettingsLearnMore" class="learnMore" is="text-link"
- data-l10n-id="network-proxy-connection-learn-more">
- </label>
- <separator orient="vertical"/>
- </hbox>
-
- <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
- <hbox>
- <button id="connectionSettings"
- is="highlightable-button"
- class="accessory-button"
- data-l10n-id="network-proxy-connection-settings"
- searchkeywords="doh trr"
- search-l10n-ids="
- connection-window.title,
- connection-proxy-option-no.label,
- connection-proxy-option-auto.label,
- connection-proxy-option-system.label,
- connection-proxy-option-manual.label,
- connection-proxy-http,
- connection-proxy-https,
- connection-proxy-http-port,
- connection-proxy-socks,
- connection-proxy-socks4,
- connection-proxy-socks5,
- connection-proxy-noproxy,
- connection-proxy-noproxy-desc,
- connection-proxy-https-sharing.label,
- connection-proxy-autotype.label,
- connection-proxy-reload.label,
- connection-proxy-autologin.label,
- connection-proxy-socks-remote-dns.label,
- connection-dns-over-https.label,
- connection-dns-over-https-url-custom.label,
- " />
- </hbox>
- </hbox>
-</groupbox>
</html:template>
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
index 2a6ba4a3d8e4..501ba9144a31 100644
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -368,15 +368,6 @@ var gMainPane = {
});
this.updatePerformanceSettingsBox({ duringChangeEvent: false });
this.displayUseSystemLocale();
- let connectionSettingsLink = document.getElementById(
- "connectionSettingsLearnMore"
- );
- let connectionSettingsUrl =
- Services.urlFormatter.formatURLPref("app.support.baseURL") +
- "prefs-connection-settings";
- connectionSettingsLink.setAttribute("href", connectionSettingsUrl);
- this.updateProxySettingsUI();
- initializeProxyUI(gMainPane);
if (Services.prefs.getBoolPref("intl.multilingual.enabled")) {
gMainPane.initBrowserLocale();
@@ -510,11 +501,6 @@ var gMainPane = {
"change",
gMainPane.updateHardwareAcceleration.bind(gMainPane)
);
- setEventListener(
- "connectionSettings",
- "command",
- gMainPane.showConnections
- );
setEventListener(
"browserContainersCheckbox",
"command",
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index a3656f827ffc..ce338584142e 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -13,6 +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 */
"use strict";
@@ -136,6 +137,14 @@ function init_all() {
register_module("paneSync", gSyncPane);
}
register_module("paneSearchResults", gSearchResultsPane);
+ if (gTorPane.enabled) {
+ document.getElementById("category-tor").hidden = false;
+ register_module("paneTor", gTorPane);
+ } else {
+ // Remove the pane from the DOM so it doesn't get incorrectly included in search results.
+ document.getElementById("template-paneTor").remove();
+ }
+
gSearchResultsPane.init();
gMainPane.preInit();
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 32184867ac17..0923005c8b90 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -12,6 +12,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
+<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [
<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
@@ -154,6 +155,9 @@
<image class="category-icon"/>
<label class="category-name" flex="1" data-l10n-id="pane-experimental-title"></label>
</richlistitem>
+
+#include ../torpreferences/content/torCategory.inc.xhtml
+
</richlistbox>
<spacer flex="1"/>
@@ -207,6 +211,7 @@
#include containers.inc.xhtml
#include sync.inc.xhtml
#include experimental.inc.xhtml
+#include ../torpreferences/content/torPane.xhtml
</vbox>
</vbox>
</vbox>
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.jsm b/browser/components/torpreferences/content/requestBridgeDialog.jsm
new file mode 100644
index 000000000000..44ae11762def
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.jsm
@@ -0,0 +1,209 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["RequestBridgeDialog"];
+
+const { BridgeDB } = ChromeUtils.import("resource:///modules/BridgeDB.jsm");
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class RequestBridgeDialog {
+ constructor() {
+ this._dialog = null;
+ this._submitButton = null;
+ this._dialogDescription = null;
+ this._captchaImage = null;
+ this._captchaEntryTextbox = null;
+ this._captchaRefreshButton = null;
+ this._incorrectCaptchaHbox = null;
+ this._incorrectCaptchaLabel = null;
+ this._bridges = [];
+ }
+
+ static get selectors() {
+ return {
+ submitButton:
+ "accept" /* not really a selector but a key for dialog's getButton */,
+ dialogDescription: "description#torPreferences-requestBridge-description",
+ captchaImage: "image#torPreferences-requestBridge-captchaImage",
+ captchaEntryTextbox: "input#torPreferences-requestBridge-captchaTextbox",
+ refreshCaptchaButton:
+ "button#torPreferences-requestBridge-refreshCaptchaButton",
+ incorrectCaptchaHbox:
+ "hbox#torPreferences-requestBridge-incorrectCaptchaHbox",
+ incorrectCaptchaLabel:
+ "label#torPreferences-requestBridge-incorrectCaptchaError",
+ };
+ }
+
+ _populateXUL(dialog) {
+ const selectors = RequestBridgeDialog.selectors;
+
+ this._dialog = dialog;
+ const dialogWin = dialog.parentElement;
+ dialogWin.setAttribute(
+ "title",
+ TorStrings.settings.requestBridgeDialogTitle
+ );
+ // user may have opened a Request Bridge dialog in another tab, so update the
+ // CAPTCHA image or close out the dialog if we have a bridge list
+ this._dialog.addEventListener("focusin", () => {
+ const uri = BridgeDB.currentCaptchaImage;
+ const bridges = BridgeDB.currentBridges;
+
+ // new captcha image
+ if (uri) {
+ this._setcaptchaImage(uri);
+ } else if (bridges) {
+ this._bridges = bridges;
+ this._submitButton.disabled = false;
+ this._dialog.cancelDialog();
+ }
+ });
+
+ this._submitButton = this._dialog.getButton(selectors.submitButton);
+ this._submitButton.setAttribute("label", TorStrings.settings.submitCaptcha);
+ this._submitButton.disabled = true;
+ this._dialog.addEventListener("dialogaccept", e => {
+ e.preventDefault();
+ this.onSubmitCaptcha();
+ });
+
+ this._dialogDescription = this._dialog.querySelector(
+ selectors.dialogDescription
+ );
+ this._dialogDescription.textContent =
+ TorStrings.settings.contactingBridgeDB;
+
+ this._captchaImage = this._dialog.querySelector(selectors.captchaImage);
+
+ // request captcha from bridge db
+ BridgeDB.requestNewCaptchaImage().then(uri => {
+ this._setcaptchaImage(uri);
+ });
+
+ this._captchaEntryTextbox = this._dialog.querySelector(
+ selectors.captchaEntryTextbox
+ );
+ this._captchaEntryTextbox.setAttribute(
+ "placeholder",
+ TorStrings.settings.captchaTextboxPlaceholder
+ );
+ this._captchaEntryTextbox.disabled = true;
+ // disable submit if entry textbox is empty
+ this._captchaEntryTextbox.oninput = () => {
+ this._submitButton.disabled = this._captchaEntryTextbox.value == "";
+ };
+
+ this._captchaRefreshButton = this._dialog.querySelector(
+ selectors.refreshCaptchaButton
+ );
+ this._captchaRefreshButton.disabled = true;
+
+ this._incorrectCaptchaHbox = this._dialog.querySelector(
+ selectors.incorrectCaptchaHbox
+ );
+ this._incorrectCaptchaLabel = this._dialog.querySelector(
+ selectors.incorrectCaptchaLabel
+ );
+ this._incorrectCaptchaLabel.setAttribute(
+ "value",
+ TorStrings.settings.incorrectCaptcha
+ );
+
+ return true;
+ }
+
+ _setcaptchaImage(uri) {
+ if (uri != this._captchaImage.src) {
+ this._captchaImage.src = uri;
+ this._dialogDescription.textContent = TorStrings.settings.solveTheCaptcha;
+ this._setUIDisabled(false);
+ this._captchaEntryTextbox.focus();
+ this._captchaEntryTextbox.select();
+ }
+ }
+
+ _setUIDisabled(disabled) {
+ this._submitButton.disabled = this._captchaGuessIsEmpty() || disabled;
+ this._captchaEntryTextbox.disabled = disabled;
+ this._captchaRefreshButton.disabled = disabled;
+ }
+
+ _captchaGuessIsEmpty() {
+ return this._captchaEntryTextbox.value == "";
+ }
+
+ init(window, dialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(dialog);
+ }, 0);
+ }
+
+ close() {
+ BridgeDB.close();
+ }
+
+ /*
+ Event Handlers
+ */
+ onSubmitCaptcha() {
+ let captchaText = this._captchaEntryTextbox.value.trim();
+ // noop if the field is empty
+ if (captchaText == "") {
+ return;
+ }
+
+ // freeze ui while we make request
+ this._setUIDisabled(true);
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.submitCaptchaGuess(captchaText)
+ .then(aBridges => {
+ if (aBridges) {
+ this._bridges = aBridges;
+ this._submitButton.disabled = false;
+ // This was successful, but use cancelDialog() to close, since
+ // we intercept the `dialogaccept` event.
+ this._dialog.cancelDialog();
+ } else {
+ this._bridges = [];
+ this._setUIDisabled(false);
+ this._incorrectCaptchaHbox.style.visibility = "visible";
+ }
+ })
+ .catch(aError => {
+ // TODO: handle other errors properly here when we do the bridge settings re-design
+ this._bridges = [];
+ this._setUIDisabled(false);
+ this._incorrectCaptchaHbox.style.visibility = "visible";
+ console.log(eError);
+ });
+ }
+
+ onRefreshCaptcha() {
+ this._setUIDisabled(true);
+ this._captchaImage.src = "";
+ this._dialogDescription.textContent =
+ TorStrings.settings.contactingBridgeDB;
+ this._captchaEntryTextbox.value = "";
+ this._incorrectCaptchaHbox.style.visibility = "hidden";
+
+ BridgeDB.requestNewCaptchaImage().then(uri => {
+ this._setcaptchaImage(uri);
+ });
+ }
+
+ openDialog(gSubDialog, aCloseCallback) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/requestBridgeDialog.xhtml",
+ {
+ features: "resizable=yes",
+ closingCallback: () => {
+ this.close();
+ aCloseCallback(this._bridges);
+ }
+ },
+ this,
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/requestBridgeDialog.xhtml b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
new file mode 100644
index 000000000000..64c4507807fb
--- /dev/null
+++ b/browser/components/torpreferences/content/requestBridgeDialog.xhtml
@@ -0,0 +1,35 @@
+<?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">
+<dialog id="torPreferences-requestBridge-dialog"
+ buttons="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>
+ <!-- init to transparent 400x125 png -->
+ <image id="torPreferences-requestBridge-captchaImage" flex="1"/>
+ <hbox id="torPreferences-requestBridge-inputHbox">
+ <html:input id="torPreferences-requestBridge-captchaTextbox" type="text" style="-moz-box-flex: 1;"/>
+ <button id="torPreferences-requestBridge-refreshCaptchaButton"
+ image="chrome://browser/skin/reload.svg"
+ oncommand="requestBridgeDialog.onRefreshCaptcha();"/>
+ </hbox>
+ <hbox id="torPreferences-requestBridge-incorrectCaptchaHbox" align="center">
+ <image id="torPreferences-requestBridge-errorIcon" />
+ <label id="torPreferences-requestBridge-incorrectCaptchaError" flex="1"/>
+ </hbox>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let requestBridgeDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-requestBridge-dialog");
+ requestBridgeDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torCategory.inc.xhtml b/browser/components/torpreferences/content/torCategory.inc.xhtml
new file mode 100644
index 000000000000..abe56200f571
--- /dev/null
+++ b/browser/components/torpreferences/content/torCategory.inc.xhtml
@@ -0,0 +1,9 @@
+<richlistitem id="category-tor"
+ class="category"
+ value="paneTor"
+ helpTopic="prefs-tor"
+ align="center"
+ hidden="true">
+ <image class="category-icon"/>
+ <label id="torPreferences-labelCategory" class="category-name" flex="1" value="Tor"/>
+</richlistitem>
diff --git a/browser/components/torpreferences/content/torLogDialog.jsm b/browser/components/torpreferences/content/torLogDialog.jsm
new file mode 100644
index 000000000000..ecc684d878c2
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.jsm
@@ -0,0 +1,66 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorLogDialog"];
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+class TorLogDialog {
+ constructor() {
+ this._dialog = null;
+ this._logTextarea = null;
+ this._copyLogButton = null;
+ }
+
+ static get selectors() {
+ return {
+ copyLogButton: "extra1",
+ logTextarea: "textarea#torPreferences-torDialog-textarea",
+ };
+ }
+
+ _populateXUL(aDialog) {
+ this._dialog = aDialog;
+ const dialogWin = this._dialog.parentElement;
+ dialogWin.setAttribute("title", TorStrings.settings.torLogDialogTitle);
+
+ this._logTextarea = this._dialog.querySelector(
+ TorLogDialog.selectors.logTextarea
+ );
+
+ this._copyLogButton = this._dialog.getButton(
+ TorLogDialog.selectors.copyLogButton
+ );
+ this._copyLogButton.setAttribute("label", TorStrings.settings.copyLog);
+ this._copyLogButton.addEventListener("command", () => {
+ this.copyTorLog();
+ });
+
+ this._logTextarea.value = TorProtocolService.getLog();
+ }
+
+ init(window, aDialog) {
+ // defer to later until firefox has populated the dialog with all our elements
+ window.setTimeout(() => {
+ this._populateXUL(aDialog);
+ }, 0);
+ }
+
+ copyTorLog() {
+ // Copy tor log messages to the system clipboard.
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ clipboard.copyString(this._logTextarea.value);
+ }
+
+ openDialog(gSubDialog) {
+ gSubDialog.open(
+ "chrome://browser/content/torpreferences/torLogDialog.xhtml",
+ { features: "resizable=yes" },
+ this
+ );
+ }
+}
diff --git a/browser/components/torpreferences/content/torLogDialog.xhtml b/browser/components/torpreferences/content/torLogDialog.xhtml
new file mode 100644
index 000000000000..9c17f8132978
--- /dev/null
+++ b/browser/components/torpreferences/content/torLogDialog.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">
+<dialog id="torPreferences-torLog-dialog"
+ buttons="accept,extra1">
+ <html:textarea
+ id="torPreferences-torDialog-textarea"
+ multiline="true"
+ readonly="true"/>
+ <script type="application/javascript"><![CDATA[
+ "use strict";
+
+ let torLogDialog = window.arguments[0];
+ let dialog = document.getElementById("torPreferences-torLog-dialog");
+ torLogDialog.init(window, dialog);
+ ]]></script>
+</dialog>
+</window>
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
new file mode 100644
index 000000000000..58eec7ff74aa
--- /dev/null
+++ b/browser/components/torpreferences/content/torPane.js
@@ -0,0 +1,940 @@
+"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
new file mode 100644
index 000000000000..7c8071f2cf10
--- /dev/null
+++ b/browser/components/torpreferences/content/torPane.xhtml
@@ -0,0 +1,157 @@
+<!-- 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
new file mode 100644
index 000000000000..b6eb0a740e5e
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -0,0 +1,189 @@
+@import url("chrome://branding/content/tor-styles.css");
+
+#category-tor > .category-icon {
+ list-style-image: url("chrome://browser/content/torpreferences/torPreferencesIcon.svg");
+}
+
+/* Connect Message Box */
+
+#torPreferences-connectMessageBox {
+ display: block;
+ position: relative;
+
+ width: auto;
+ min-height: 32px;
+ border-radius: 4px;
+ padding: 8px;
+}
+
+#torPreferences-connectMessageBox.hidden {
+ display: none;
+}
+
+#torPreferences-connectMessageBox.error {
+ background-color: var(--red-60);
+ color: white;
+}
+
+#torPreferences-connectMessageBox.warning {
+ background-color: var(--purple-50);
+ color: white;
+}
+
+#torPreferences-connectMessageBox table {
+ border-collapse: collapse;
+}
+
+#torPreferences-connectMessageBox td {
+ vertical-align: middle;
+}
+
+#torPreferences-connectMessageBox td:first-child {
+ width: 16px;
+}
+
+#torPreferences-connectMessageBox-icon {
+ width: 16px;
+ height: 16px;
+
+ mask-repeat: no-repeat !important;
+ mask-size: 16px !important;
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-icon
+{
+ mask: url("chrome://browser/skin/onion-slash.svg");
+ background-color: white;
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-icon
+{
+ mask: url("chrome://browser/skin/onion.svg");
+ background-color: white;
+}
+
+#torPreferences-connectMessageBox-message {
+ line-height: 16px;
+ font-size: 1.11em;
+ padding-left: 8px!important;
+}
+
+#torPreferences-connectMessageBox-button {
+ display: block;
+ width: auto;
+
+ border-radius: 4px;
+ border: 0;
+
+ padding-inline: 18px;
+ padding-block: 8px;
+ margin-block: 0px;
+ margin-inline-start: 8px;
+ margin-inline-end: 0px;
+
+ font-size: 1.0em;
+ font-weight: 600;
+ white-space: nowrap;
+
+ color: white;
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button {
+ background-color: var(--red-70);
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:hover {
+ background-color: var(--red-80);
+}
+
+#torPreferences-connectMessageBox.error #torPreferences-connectMessageBox-button:active {
+ background-color: var(--red-90);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button {
+ background-color: var(--purple-70);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:hover {
+ background-color: var(--purple-80);
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:active {
+ background-color: var(--purple-90);
+}
+
+/* Advanced Settings */
+
+#torPreferences-advanced-grid {
+ display: grid;
+ grid-template-columns: auto 1fr;
+}
+
+.torPreferences-advanced-checkbox-container {
+ grid-column: 1 / 3;
+}
+
+#torPreferences-localProxy-textboxAddress,
+#torPreferences-localProxy-textboxUsername,
+#torPreferences-localProxy-textboxPassword,
+#torPreferences-advanced-textboxAllowedPorts {
+ -moz-box-flex: 1;
+}
+
+hbox#torPreferences-torDaemon-hbox {
+ margin-top: 20px;
+}
+
+description#torPreferences-requestBridge-description {
+ /*margin-bottom: 1em;*/
+ min-height: 2em;
+}
+
+image#torPreferences-requestBridge-captchaImage {
+ margin: 1em;
+ min-height: 125px;
+}
+
+button#torPreferences-requestBridge-refreshCaptchaButton {
+ min-width: initial;
+}
+
+dialog#torPreferences-requestBridge-dialog > hbox {
+ margin-bottom: 1em;
+}
+
+/*
+ Various elements that really should be lining up don't because they have inconsistent margins
+*/
+.torMarginFix {
+ margin-left : 4px;
+ margin-right : 4px;
+}
+
+/*
+ This hbox is hidden by css here by default so that the
+ xul dialog allocates enough screen space for the error message
+ element, otherwise it gets cut off since dialog's overflow is hidden
+*/
+hbox#torPreferences-requestBridge-incorrectCaptchaHbox {
+ visibility: hidden;
+}
+
+image#torPreferences-requestBridge-errorIcon {
+ list-style-image: url("chrome://browser/skin/warning.svg");
+}
+
+groupbox#torPreferences-bridges-group textarea {
+ white-space: pre;
+ overflow: auto;
+}
+
+textarea#torPreferences-torDialog-textarea {
+ -moz-box-flex: 1;
+ font-family: monospace;
+ font-size: 0.8em;
+ white-space: pre;
+ overflow: auto;
+ /* 10 lines */
+ min-height: 20em;
+}
\ No newline at end of file
diff --git a/browser/components/torpreferences/content/torPreferencesIcon.svg b/browser/components/torpreferences/content/torPreferencesIcon.svg
new file mode 100644
index 000000000000..382a061774aa
--- /dev/null
+++ b/browser/components/torpreferences/content/torPreferencesIcon.svg
@@ -0,0 +1,8 @@
+<svg fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 16 16" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
+ <g clip-rule="evenodd" fill-rule="evenodd">
+ <path d="m11 8c0 1.65686-1.34314 3-3 3-1.65685 0-3-1.34314-3-3 0-1.65685 1.34315-3 3-3 1.65686 0 3 1.34315 3 3zm-1.17187 0c0 1.00965-.81848 1.82813-1.82813 1.82813-1.00964 0-1.82812-.81848-1.82812-1.82813 0-1.00964.81848-1.82812 1.82812-1.82812 1.00965 0 1.82813.81848 1.82813 1.82812z"/>
+ <path d="m7.99999 13.25c2.89951 0 5.25001-2.3505 5.25001-5.25001 0-2.89949-2.3505-5.25-5.25001-5.25-2.89949 0-5.25 2.35051-5.25 5.25 0 2.89951 2.35051 5.25001 5.25 5.25001zm0-1.1719c2.25231 0 4.07811-1.8258 4.07811-4.07811 0-2.25228-1.8258-4.07812-4.07811-4.07812-2.25228 0-4.07812 1.82584-4.07812 4.07812 0 2.25231 1.82584 4.07811 4.07812 4.07811z"/>
+ <path d="m8 15.5c4.1421 0 7.5-3.3579 7.5-7.5 0-4.14214-3.3579-7.5-7.5-7.5-4.14214 0-7.5 3.35786-7.5 7.5 0 4.1421 3.35786 7.5 7.5 7.5zm0-1.1719c3.4949 0 6.3281-2.8332 6.3281-6.3281 0-3.49493-2.8332-6.32812-6.3281-6.32812-3.49493 0-6.32812 2.83319-6.32812 6.32812 0 3.4949 2.83319 6.3281 6.32812 6.3281z"/>
+ </g>
+ <path d="m.5 8c0 4.1421 3.35786 7.5 7.5 7.5v-15c-4.14214 0-7.5 3.35786-7.5 7.5z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/components/torpreferences/jar.mn b/browser/components/torpreferences/jar.mn
new file mode 100644
index 000000000000..552c92b2feff
--- /dev/null
+++ b/browser/components/torpreferences/jar.mn
@@ -0,0 +1,10 @@
+browser.jar:
+ 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/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/torPreferences.css (content/torPreferences.css)
+ content/browser/torpreferences/torPreferencesIcon.svg (content/torPreferencesIcon.svg)
diff --git a/browser/components/torpreferences/moz.build b/browser/components/torpreferences/moz.build
new file mode 100644
index 000000000000..2661ad7cb9f3
--- /dev/null
+++ b/browser/components/torpreferences/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ["jar.mn"]
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] 40209: Implement Basic Crypto Safety
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit fde945b87f4191e13b1400993dee98d6cbed5eb8
Author: sanketh <me(a)snkth.com>
Date: Mon Feb 8 20:12:44 2021 -0500
40209: Implement Basic Crypto Safety
Adds a CryptoSafety actor which detects when you've copied a crypto
address from a HTTP webpage and shows a warning.
Closes #40209.
Bug 40428: Fix string attribute names
---
browser/actors/CryptoSafetyChild.jsm | 87 ++++++++++++++++
browser/actors/CryptoSafetyParent.jsm | 142 +++++++++++++++++++++++++++
browser/actors/moz.build | 2 +
browser/base/content/popup-notifications.inc | 14 +++
browser/components/BrowserGlue.jsm | 18 ++++
browser/modules/TorStrings.jsm | 48 +++++++++
browser/themes/shared/browser.inc.css | 5 +
toolkit/content/license.html | 32 ++++++
toolkit/modules/Bech32Decode.jsm | 103 +++++++++++++++++++
toolkit/modules/moz.build | 1 +
10 files changed, 452 insertions(+)
diff --git a/browser/actors/CryptoSafetyChild.jsm b/browser/actors/CryptoSafetyChild.jsm
new file mode 100644
index 000000000000..87ff261d4915
--- /dev/null
+++ b/browser/actors/CryptoSafetyChild.jsm
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Copyright (c) 2020, The Tor Project, Inc.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["CryptoSafetyChild"];
+
+const { Bech32Decode } = ChromeUtils.import(
+ "resource://gre/modules/Bech32Decode.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const kPrefCryptoSafety = "security.cryptoSafety";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "isCryptoSafetyEnabled",
+ kPrefCryptoSafety,
+ true /* defaults to true */
+);
+
+function looksLikeCryptoAddress(s) {
+ // P2PKH and P2SH addresses
+ // https://stackoverflow.com/a/24205650
+ const bitcoinAddr = /^[13][a-km-zA-HJ-NP-Z1-9]{25,39}$/;
+ if (bitcoinAddr.test(s)) {
+ return true;
+ }
+
+ // Bech32 addresses
+ if (Bech32Decode(s) !== null) {
+ return true;
+ }
+
+ // regular addresses
+ const etherAddr = /^0x[a-fA-F0-9]{40}$/;
+ if (etherAddr.test(s)) {
+ return true;
+ }
+
+ // t-addresses
+ // https://www.reddit.com/r/zec/comments/8mxj6x/simple_regex_to_validate_a_zca…
+ const zcashAddr = /^t1[a-zA-Z0-9]{33}$/;
+ if (zcashAddr.test(s)) {
+ return true;
+ }
+
+ // Standard, Integrated, and 256-bit Integrated addresses
+ // https://monero.stackexchange.com/a/10627
+ const moneroAddr = /^4(?:[0-9AB]|[1-9A-HJ-NP-Za-km-z]{12}(?:[1-9A-HJ-NP-Za-km-z]{30})?)[1-9A-HJ-NP-Za-km-z]{93}$/;
+ if (moneroAddr.test(s)) {
+ return true;
+ }
+
+ return false;
+}
+
+class CryptoSafetyChild extends JSWindowActorChild {
+ handleEvent(event) {
+ if (isCryptoSafetyEnabled) {
+ // Ignore non-HTTP addresses
+ if (!this.document.documentURIObject.schemeIs("http")) {
+ return;
+ }
+ // Ignore onion addresses
+ if (this.document.documentURIObject.host.endsWith(".onion")) {
+ return;
+ }
+
+ if (event.type == "copy" || event.type == "cut") {
+ this.contentWindow.navigator.clipboard.readText().then(clipText => {
+ const selection = clipText.trim();
+ if (looksLikeCryptoAddress(selection)) {
+ this.sendAsyncMessage("CryptoSafety:CopiedText", {
+ selection,
+ });
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/browser/actors/CryptoSafetyParent.jsm b/browser/actors/CryptoSafetyParent.jsm
new file mode 100644
index 000000000000..bac151df5511
--- /dev/null
+++ b/browser/actors/CryptoSafetyParent.jsm
@@ -0,0 +1,142 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Copyright (c) 2020, The Tor Project, Inc.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ["CryptoSafetyParent"];
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ TorStrings: "resource:///modules/TorStrings.jsm",
+});
+
+const kPrefCryptoSafety = "security.cryptoSafety";
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "isCryptoSafetyEnabled",
+ kPrefCryptoSafety,
+ true /* defaults to true */
+);
+
+class CryptoSafetyParent extends JSWindowActorParent {
+ getBrowser() {
+ return this.browsingContext.top.embedderElement;
+ }
+
+ receiveMessage(aMessage) {
+ if (isCryptoSafetyEnabled) {
+ if (aMessage.name == "CryptoSafety:CopiedText") {
+ showPopup(this.getBrowser(), aMessage.data.selection);
+ }
+ }
+ }
+}
+
+function trimAddress(cryptoAddr) {
+ if (cryptoAddr.length <= 32) {
+ return cryptoAddr;
+ }
+ return cryptoAddr.substring(0, 32) + "...";
+}
+
+function showPopup(aBrowser, cryptoAddr) {
+ const chromeDoc = aBrowser.ownerDocument;
+ if (chromeDoc) {
+ const win = chromeDoc.defaultView;
+ const cryptoSafetyPrompt = new CryptoSafetyPrompt(
+ aBrowser,
+ win,
+ cryptoAddr
+ );
+ cryptoSafetyPrompt.show();
+ }
+}
+
+class CryptoSafetyPrompt {
+ constructor(aBrowser, aWin, cryptoAddr) {
+ this._browser = aBrowser;
+ this._win = aWin;
+ this._cryptoAddr = cryptoAddr;
+ }
+
+ show() {
+ const primaryAction = {
+ label: TorStrings.cryptoSafetyPrompt.primaryAction,
+ accessKey: TorStrings.cryptoSafetyPrompt.primaryActionAccessKey,
+ callback: () => {
+ this._win.torbutton_new_circuit();
+ },
+ };
+
+ const secondaryAction = {
+ label: TorStrings.cryptoSafetyPrompt.secondaryAction,
+ accessKey: TorStrings.cryptoSafetyPrompt.secondaryActionAccessKey,
+ callback: () => {},
+ };
+
+ let _this = this;
+ const options = {
+ popupIconURL: "chrome://browser/skin/cert-error.svg",
+ eventCallback(aTopic) {
+ if (aTopic === "showing") {
+ _this._onPromptShowing();
+ }
+ },
+ };
+
+ const cryptoWarningText = TorStrings.cryptoSafetyPrompt.cryptoWarning.replace(
+ "%S",
+ trimAddress(this._cryptoAddr)
+ );
+
+ if (this._win.PopupNotifications) {
+ this._prompt = this._win.PopupNotifications.show(
+ this._browser,
+ "crypto-safety-warning",
+ cryptoWarningText,
+ null /* anchor ID */,
+ primaryAction,
+ [secondaryAction],
+ options
+ );
+ }
+ }
+
+ _onPromptShowing() {
+ let xulDoc = this._browser.ownerDocument;
+
+ let whatCanHeading = xulDoc.getElementById(
+ "crypto-safety-warning-notification-what-can-heading"
+ );
+ if (whatCanHeading) {
+ whatCanHeading.textContent = TorStrings.cryptoSafetyPrompt.whatCanHeading;
+ }
+
+ let whatCanBody = xulDoc.getElementById(
+ "crypto-safety-warning-notification-what-can-body"
+ );
+ if (whatCanBody) {
+ whatCanBody.textContent = TorStrings.cryptoSafetyPrompt.whatCanBody;
+ }
+
+ let learnMoreElem = xulDoc.getElementById(
+ "crypto-safety-warning-notification-learnmore"
+ );
+ if (learnMoreElem) {
+ learnMoreElem.setAttribute(
+ "value",
+ TorStrings.cryptoSafetyPrompt.learnMore
+ );
+ learnMoreElem.setAttribute(
+ "href",
+ TorStrings.cryptoSafetyPrompt.learnMoreURL
+ );
+ }
+ }
+}
diff --git a/browser/actors/moz.build b/browser/actors/moz.build
index 28c981625a7f..48e3dde6b832 100644
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -56,6 +56,8 @@ FINAL_TARGET_FILES.actors += [
"ContentSearchParent.jsm",
"ContextMenuChild.jsm",
"ContextMenuParent.jsm",
+ "CryptoSafetyChild.jsm",
+ "CryptoSafetyParent.jsm",
"DecoderDoctorChild.jsm",
"DecoderDoctorParent.jsm",
"DOMFullscreenChild.jsm",
diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
index 6adfde017b9e..8f6d28cc81b2 100644
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -162,3 +162,17 @@
</vbox>
</popupnotificationfooter>
</popupnotification>
+
+ <popupnotification id="crypto-safety-warning-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <description id="crypto-safety-warning-notification-desc"/>
+ <html:div id="crypto-safety-warning-notification-what-can">
+ <html:strong id="crypto-safety-warning-notification-what-can-heading" />
+ <html:br/>
+ <html:span id="crypto-safety-warning-notification-what-can-body" />
+ </html:div>
+ <label id="crypto-safety-warning-notification-learnmore"
+ class="popup-notification-learnmore-link"
+ is="text-link"/>
+ </popupnotificationcontent>
+ </popupnotification>
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 0386e4159eeb..0e0138fe2fd9 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -432,6 +432,24 @@ let JSWINDOWACTORS = {
},
messageManagerGroups: ["browsers"],
+
+ allFrames: true,
+ },
+
+ CryptoSafety: {
+ parent: {
+ moduleURI: "resource:///actors/CryptoSafetyParent.jsm",
+ },
+
+ child: {
+ moduleURI: "resource:///actors/CryptoSafetyChild.jsm",
+ group: "browsers",
+ events: {
+ copy: { mozSystemGroup: true },
+ cut: { mozSystemGroup: true },
+ },
+ },
+
allFrames: true,
},
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index e8a8d37ae373..1e08b168e4af 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -101,6 +101,54 @@ class TorPropertyStringBundle {
Security Level Strings
*/
var TorStrings = {
+ /*
+ CryptoSafetyPrompt Strings
+ */
+ cryptoSafetyPrompt: (function() {
+ let tsb = new TorPropertyStringBundle(
+ "chrome://torbutton/locale/torbutton.properties",
+ "cryptoSafetyPrompt."
+ );
+ let getString = function(key, fallback) {
+ return tsb.getString(key, fallback);
+ };
+
+ let retval = {
+ cryptoWarning: getString(
+ "cryptoWarning",
+ "A cryptocurrency address (%S) has been copied from an insecure website. It could have been modified."
+ ),
+ whatCanHeading: getString(
+ "whatCanHeading",
+ "What can you do about it?"
+ ),
+ whatCanBody: getString(
+ "whatCanBody",
+ "You can try reconnecting with a new circuit to establish a secure connection, or accept the risk and dismiss this warning."
+ ),
+ learnMore: getString("learnMore", "Learn more"),
+ learnMoreURL: `https://support.torproject.org/${getLocale()}/`,
+ primaryAction: getString(
+ "primaryAction",
+ "Reload Tab with a New Circuit"
+ ),
+ primaryActionAccessKey: getString(
+ "primaryActionAccessKey",
+ "R"
+ ),
+ secondaryAction: getString(
+ "secondaryAction",
+ "Dismiss Warning"
+ ),
+ secondaryActionAccessKey: getString(
+ "secondaryActionAccessKey",
+ "D"
+ ),
+ };
+
+ return retval;
+ })() /* CryptoSafetyPrompt Strings */,
+
/*
Tor Browser Security Level Strings
*/
diff --git a/browser/themes/shared/browser.inc.css b/browser/themes/shared/browser.inc.css
index 2eeefda472d6..e70aeab1c761 100644
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -828,3 +828,8 @@ popupnotificationcontent {
#tab-notification-deck {
display: block;
}
+
+#crypto-safety-warning-notification-what-can {
+ display: block;
+ margin: 5px;
+}
diff --git a/toolkit/content/license.html b/toolkit/content/license.html
index d26dc7118d3c..782e874edf2a 100644
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -70,6 +70,7 @@
<li><a href="about:license#arm">ARM License</a></li>
<li><a href="about:license#babel">Babel License</a></li>
<li><a href="about:license#babylon">Babylon License</a></li>
+ <li><a href="about:license#bech32">Bech32 License</a></li>
<li><a href="about:license#bincode">bincode License</a></li>
<li><a href="about:license#bsd2clause">BSD 2-Clause License</a></li>
<li><a href="about:license#bsd3clause">BSD 3-Clause License</a></li>
@@ -2105,6 +2106,37 @@ furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+
+ <hr>
+
+ <h1><a id="bech32"></a>Bech32 License</h1>
+
+ <p>This license applies to the file
+ <code>toolkit/modules/Bech32Decode.jsm</code>.
+ </p>
+
+<pre>
+Copyright (c) 2017 Pieter Wuille
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
diff --git a/toolkit/modules/Bech32Decode.jsm b/toolkit/modules/Bech32Decode.jsm
new file mode 100644
index 000000000000..3a2bc7ae0a10
--- /dev/null
+++ b/toolkit/modules/Bech32Decode.jsm
@@ -0,0 +1,103 @@
+// Adapted from the reference implementation of Bech32
+// https://github.com/sipa/bech32
+
+// Copyright (c) 2017 Pieter Wuille
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+"use strict";
+
+/**
+ * JS module implementation of Bech32 decoding adapted from the reference
+ * implementation https://github.com/sipa/bech32.
+ */
+
+var EXPORTED_SYMBOLS = ["Bech32Decode"];
+
+var CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
+var GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
+
+function polymod(values) {
+ var chk = 1;
+ for (var p = 0; p < values.length; ++p) {
+ var top = chk >> 25;
+ chk = ((chk & 0x1ffffff) << 5) ^ values[p];
+ for (var i = 0; i < 5; ++i) {
+ if ((top >> i) & 1) {
+ chk ^= GENERATOR[i];
+ }
+ }
+ }
+ return chk;
+}
+
+function hrpExpand(hrp) {
+ var ret = [];
+ var p;
+ for (p = 0; p < hrp.length; ++p) {
+ ret.push(hrp.charCodeAt(p) >> 5);
+ }
+ ret.push(0);
+ for (p = 0; p < hrp.length; ++p) {
+ ret.push(hrp.charCodeAt(p) & 31);
+ }
+ return ret;
+}
+
+function verifyChecksum(hrp, data) {
+ return polymod(hrpExpand(hrp).concat(data)) === 1;
+}
+
+function Bech32Decode(bechString) {
+ var p;
+ var has_lower = false;
+ var has_upper = false;
+ for (p = 0; p < bechString.length; ++p) {
+ if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
+ return null;
+ }
+ if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
+ has_lower = true;
+ }
+ if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
+ has_upper = true;
+ }
+ }
+ if (has_lower && has_upper) {
+ return null;
+ }
+ bechString = bechString.toLowerCase();
+ var pos = bechString.lastIndexOf("1");
+ if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
+ return null;
+ }
+ var hrp = bechString.substring(0, pos);
+ var data = [];
+ for (p = pos + 1; p < bechString.length; ++p) {
+ var d = CHARSET.indexOf(bechString.charAt(p));
+ if (d === -1) {
+ return null;
+ }
+ data.push(d);
+ }
+ if (!verifyChecksum(hrp, data)) {
+ return null;
+ }
+ return { hrp: hrp, data: data.slice(0, data.length - 6) };
+}
diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build
index 9d349d9f3394..a3bfdf83ffbd 100644
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -152,6 +152,7 @@ EXTRA_JS_MODULES += [
"ActorManagerParent.jsm",
"AppMenuNotifications.jsm",
"AsyncPrefs.jsm",
+ "Bech32Decode.jsm",
"BinarySearch.jsm",
"BrowserTelemetryUtils.jsm",
"BrowserUtils.jsm",
1
0