tor-commits
Threads by month
- ----- 2025 -----
- 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
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
October 2021
- 15 participants
- 2054 discussions

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 40125: Expose Security Level pref in GeckoView
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit c47a62841092c96aaa14120fcdbf7be63c6d8c60
Author: Matthew Finkel <sysrqb(a)torproject.org>
Date: Mon Sep 14 02:52:28 2020 +0000
Bug 40125: Expose Security Level pref in GeckoView
---
mobile/android/geckoview/api.txt | 3 ++
.../mozilla/geckoview/GeckoRuntimeSettings.java | 33 ++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt
index 5a04541ae57f..f9f0c9d14a8a 100644
-…
[View More]-- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -738,6 +738,7 @@ package org.mozilla.geckoview {
method @Nullable public GeckoRuntime getRuntime();
method @Nullable public Rect getScreenSizeOverride();
method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate();
+ method public int getTorSecurityLevel();
method public boolean getUseMaxScreenDepth();
method public boolean getWebFontsEnabled();
method public boolean getWebManifestEnabled();
@@ -758,6 +759,7 @@ package org.mozilla.geckoview {
method @NonNull public GeckoRuntimeSettings setLoginAutofillEnabled(boolean);
method @NonNull public GeckoRuntimeSettings setPreferredColorScheme(int);
method @NonNull public GeckoRuntimeSettings setRemoteDebuggingEnabled(boolean);
+ method @NonNull public GeckoRuntimeSettings setTorSecurityLevel(int);
method @NonNull public GeckoRuntimeSettings setWebFontsEnabled(boolean);
method @NonNull public GeckoRuntimeSettings setWebManifestEnabled(boolean);
field public static final int ALLOW_ALL = 0;
@@ -798,6 +800,7 @@ package org.mozilla.geckoview {
method @NonNull public GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean);
method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int);
method @NonNull public GeckoRuntimeSettings.Builder telemetryDelegate(@NonNull RuntimeTelemetry.Delegate);
+ method @NonNull public GeckoRuntimeSettings.Builder torSecurityLevel(int);
method @NonNull public GeckoRuntimeSettings.Builder useMaxScreenDepth(boolean);
method @NonNull public GeckoRuntimeSettings.Builder webFontsEnabled(boolean);
method @NonNull public GeckoRuntimeSettings.Builder webManifest(boolean);
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
index 5de0b00f7a0c..c573ee7688f3 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -472,6 +472,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
getSettings().setAllowInsecureConnections(level);
return this;
}
+
+ /**
+ * Set security level.
+ *
+ * @param level A value determining the security level. Default is 0.
+ * @return This Builder instance.
+ */
+ public @NonNull Builder torSecurityLevel(final int level) {
+ getSettings().mTorSecurityLevel.set(level);
+ return this;
+ }
}
private GeckoRuntime mRuntime;
@@ -528,6 +539,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
"dom.security.https_only_mode_pbm", false);
/* package */ final Pref<Integer> mProcessCount = new Pref<>(
"dom.ipc.processCount", 2);
+ /* package */ final Pref<Integer> mTorSecurityLevel = new Pref<>(
+ "extensions.torbutton.security_slider", 4);
/* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
@@ -1280,6 +1293,26 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
return this;
}
+ /**
+ * Gets the current security level.
+ *
+ * @return current security protection level
+ */
+ public int getTorSecurityLevel() {
+ return mTorSecurityLevel.get();
+ }
+
+ /**
+ * Sets the Tor Security Level.
+ *
+ * @param level security protection level
+ * @return This GeckoRuntimeSettings instance.
+ */
+ public @NonNull GeckoRuntimeSettings setTorSecurityLevel(final int level) {
+ mTorSecurityLevel.commit(level);
+ return this;
+ }
+
@Override // Parcelable
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 40002: Remove about:ion
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 6271c246c67bcf9dd074a01491dacf44b8809b9d
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Fri Aug 14 09:06:33 2020 -0400
Bug 40002: Remove about:ion
Firefox Ion (previously Firefox Pioneer) is an opt-in program in which people
volunteer to participate in studies that collect detailed, sensitive data about
how they use their browser.
---
browser/components/about/AboutRedirector.cpp | 2 --
browser/components/about/components.conf | 1 -
2 files …
[View More]changed, 3 deletions(-)
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
index e6cf3fe0ef9f..323c1b6fb653 100644
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -122,8 +122,6 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::HIDE_FROM_ABOUTABOUT},
{"restartrequired", "chrome://browser/content/aboutRestartRequired.xhtml",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT},
- {"ion", "chrome://browser/content/ion.html",
- nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT},
#ifdef TOR_BROWSER_UPDATE
{"tbupdate", "chrome://browser/content/abouttbupdate/aboutTBUpdate.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf
index faf6107d6fa6..67f178ee23ff 100644
--- a/browser/components/about/components.conf
+++ b/browser/components/about/components.conf
@@ -13,7 +13,6 @@ pages = [
'logins',
'loginsimportreport',
'newtab',
- 'ion',
'pocket-home',
'pocket-saved',
'pocket-signup',
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 30605: Honor privacy.spoof_english in Android
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit dd4ad53b9eb4585359eb607cd2af566283a552ff
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Oct 16 10:45:17 2020 +0200
Bug 30605: Honor privacy.spoof_english in Android
This checks `privacy.spoof_english` whenever `setLocales` is
called from Fenix side and sets `intl.accept_languages`
accordingly.
---
mobile/android/components/geckoview/GeckoViewStartup.jsm | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/mobile/android/components/geckoview/…
[View More]GeckoViewStartup.jsm b/mobile/android/components/geckoview/GeckoViewStartup.jsm
index 055c3da638e1..2bf394f2cb3b 100644
--- a/mobile/android/components/geckoview/GeckoViewStartup.jsm
+++ b/mobile/android/components/geckoview/GeckoViewStartup.jsm
@@ -17,6 +17,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
EventDispatcher: "resource://gre/modules/Messaging.jsm",
Preferences: "resource://gre/modules/Preferences.jsm",
Services: "resource://gre/modules/Services.jsm",
+ RFPHelper: "resource://gre/modules/RFPHelper.jsm",
});
const { debug, warn } = GeckoViewUtils.initLogging("Startup");
@@ -255,6 +256,10 @@ class GeckoViewStartup {
if (aData.requestedLocales) {
Services.locale.requestedLocales = aData.requestedLocales;
}
+ RFPHelper._handleSpoofEnglishChanged();
+ if (Services.prefs.getIntPref("privacy.spoof_english", 0) === 2) {
+ break;
+ }
const pls = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(
Ci.nsIPrefLocalizedString
);
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 5817e8746a5718a3a78f2f0685c453d4c65d34b4
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Sep 4 12:34:35 2020 +0200
Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop
This loads HTTPS Everywhere as a builtin addon from a hardcoded
resource:// URI in desktop. It also ensures that the non-builtin
HTTPS Everywhere addon is always uninstalled on browser startup.
The reason of making this desktop-only is that there are some issues
…
[View More]when installing a builtin extension from geckoview side, making
the extension not available on first startup. So, at least for
now we handle the Fenix case separately. See #40118 for a followup
for investigating these.
---
browser/components/BrowserGlue.jsm | 37 ++++++++++++++++++++++
toolkit/components/extensions/Extension.jsm | 10 ++++--
.../mozapps/extensions/internal/XPIProvider.jsm | 13 ++++++++
3 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 04b51b1c2cc1..d7acae6d8f9d 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -45,6 +45,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
DownloadsViewableInternally:
"resource:///modules/DownloadsViewableInternally.jsm",
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
+ ExtensionData: "resource://gre/modules/Extension.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
FeatureGate: "resource://featuregates/FeatureGate.jsm",
FxAccounts: "resource://gre/modules/FxAccounts.jsm",
@@ -120,6 +121,13 @@ XPCOMUtils.defineLazyServiceGetters(this, {
PushService: ["@mozilla.org/push/Service;1", "nsIPushService"],
});
+XPCOMUtils.defineLazyServiceGetters(this, {
+ resProto: [
+ "@mozilla.org/network/protocol;1?name=resource",
+ "nsISubstitutingProtocolHandler",
+ ],
+});
+
const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
/**
@@ -1382,6 +1390,35 @@ BrowserGlue.prototype = {
"resource://builtin-themes/alpenglow/"
);
+ // Install https-everywhere builtin addon if needed.
+ (async () => {
+ const HTTPS_EVERYWHERE_ID = "https-everywhere-eff(a)eff.org";
+ const HTTPS_EVERYWHERE_BUILTIN_URL =
+ "resource://torbutton/content/extensions/https-everywhere/";
+ // This does something similar as GeckoViewWebExtension.jsm: it tries
+ // to load the manifest to retrieve the version of the builtin and
+ // compares it to the currently installed one to see whether we need
+ // to install or not. Here we delegate that to
+ // AddonManager.maybeInstallBuiltinAddon.
+ try {
+ const resolvedURI = Services.io.newURI(
+ resProto.resolveURI(Services.io.newURI(HTTPS_EVERYWHERE_BUILTIN_URL))
+ );
+ const extensionData = new ExtensionData(resolvedURI);
+ const manifest = await extensionData.loadManifest();
+
+ await AddonManager.maybeInstallBuiltinAddon(
+ HTTPS_EVERYWHERE_ID,
+ manifest.version,
+ HTTPS_EVERYWHERE_BUILTIN_URL
+ );
+ } catch (e) {
+ const log = Log.repository.getLogger("HttpsEverywhereBuiltinLoader");
+ log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
+ log.error("Could not install https-everywhere extension", e);
+ }
+ })();
+
if (AppConstants.MOZ_NORMANDY) {
Normandy.init();
}
diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm
index 618647af9150..69833684ca53 100644
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -267,6 +267,7 @@ const LOGGER_ID_BASE = "addons.webextension.";
const UUID_MAP_PREF = "extensions.webextensions.uuids";
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
+const PERSISTENT_EXTENSIONS = new Set(["https-everywhere-eff(a)eff.org"]);
const COMMENT_REGEXP = new RegExp(
String.raw`
@@ -413,7 +414,8 @@ var ExtensionAddonObserver = {
);
}
- if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)) {
+ if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) &&
+ !PERSISTENT_EXTENSIONS.has(addon.id)) {
// Clear browser.storage.local backends.
AsyncShutdown.profileChangeTeardown.addBlocker(
`Clear Extension Storage ${addon.id} (File Backend)`,
@@ -461,7 +463,8 @@ var ExtensionAddonObserver = {
ExtensionPermissions.removeAll(addon.id);
- if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false)) {
+ if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false) &&
+ !PERSISTENT_EXTENSIONS.has(addon.id)) {
// Clear the entry in the UUID map
UUIDMap.remove(addon.id);
}
@@ -2696,7 +2699,8 @@ class Extension extends ExtensionData {
);
} else if (
this.startupReason === "ADDON_INSTALL" &&
- !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)
+ !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) &&
+ !PERSISTENT_EXTENSIONS.has(this.id)
) {
// If the extension has been just installed, set it as migrated,
// because there will not be any data to migrate.
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index ba562c92948d..dea49a8e9f79 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1501,6 +1501,19 @@ var XPIStates = {
continue;
}
+ // Uninstall HTTPS Everywhere if it is installed in the user profile.
+ if (
+ id === "https-everywhere-eff(a)eff.org" &&
+ loc.name === KEY_APP_PROFILE
+ ) {
+ logger.debug(
+ "Uninstalling the HTTPS Everywhere extension from user profile."
+ );
+ loc.installer.uninstallAddon(id);
+ changed = true;
+ continue;
+ }
+
let xpiState = loc.get(id);
if (!xpiState) {
// If the location is not supported for sideloading, skip new
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 40171: Make WebRequest and GeckoWebExecutor First-Party aware
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 331dc59a444c2ab8ce30648c1616973f808f29c4
Author: Alex Catarineu <acat(a)torproject.org>
Date: Wed Nov 4 15:58:22 2020 +0100
Bug 40171: Make WebRequest and GeckoWebExecutor First-Party aware
---
.../main/java/org/mozilla/geckoview/WebRequest.java | 18 ++++++++++++++++++
widget/android/WebExecutorSupport.cpp | 10 ++++++++++
2 files changed, 28 insertions(+)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java b/…
[View More]mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java
index d1d6e06b7396..4e17bc034edb 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebRequest.java
@@ -51,6 +51,11 @@ public class WebRequest extends WebMessage {
*/
public final @Nullable String referrer;
+ /**
+ * The value of the origin of this request.
+ */
+ public final @Nullable String origin;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef({CACHE_MODE_DEFAULT, CACHE_MODE_NO_STORE,
CACHE_MODE_RELOAD, CACHE_MODE_NO_CACHE,
@@ -112,6 +117,7 @@ public class WebRequest extends WebMessage {
method = builder.mMethod;
cacheMode = builder.mCacheMode;
referrer = builder.mReferrer;
+ origin = builder.mOrigin;
if (builder.mBody != null) {
body = builder.mBody.asReadOnlyBuffer();
@@ -128,6 +134,7 @@ public class WebRequest extends WebMessage {
/* package */ String mMethod = "GET";
/* package */ int mCacheMode = CACHE_MODE_DEFAULT;
/* package */ String mReferrer;
+ /* package */ String mOrigin;
/**
* Construct a Builder instance with the specified URI.
@@ -226,6 +233,17 @@ public class WebRequest extends WebMessage {
return this;
}
+ /**
+ * Set the origin URI.
+ *
+ * @param origin A URI String
+ * @return This Builder instance.
+ */
+ public @NonNull Builder origin(final @Nullable String origin) {
+ mOrigin = origin;
+ return this;
+ }
+
/**
* @return A {@link WebRequest} constructed with the values from this Builder instance.
*/
diff --git a/widget/android/WebExecutorSupport.cpp b/widget/android/WebExecutorSupport.cpp
index 99e7de95a0fb..bbdcc8f36bd6 100644
--- a/widget/android/WebExecutorSupport.cpp
+++ b/widget/android/WebExecutorSupport.cpp
@@ -393,6 +393,16 @@ nsresult WebExecutorSupport::CreateStreamLoader(
MOZ_ASSERT(cookieJarSettings);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ RefPtr<nsIURI> originUri;
+ const auto origin = req->Origin();
+ if (origin) {
+ rv = NS_NewURI(getter_AddRefs(originUri), origin->ToString());
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ attrs.SetFirstPartyDomain(true, originUri);
+ loadInfo->SetOriginAttributes(attrs);
+ }
loadInfo->SetCookieJarSettings(cookieJarSettings);
// setup http/https specific things
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 33852: Clean up about:logins (LockWise) to avoid mentioning sync, etc.
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 09397d656f8e5236ced9c18f09a5e2593d2defd3
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Tue Jul 14 11:15:07 2020 -0400
Bug 33852: Clean up about:logins (LockWise) to avoid mentioning sync, etc.
Hide elements on about:logins that mention sync, "Firefox LockWise", and
Mozilla's LockWise mobile apps.
Disable the "Create New Login" button when security.nocertdb is true.
---
browser/components/aboutlogins/AboutLoginsParent.jsm | 2 ++
…
[View More]browser/components/aboutlogins/content/aboutLogins.css | 8 +++++++-
browser/components/aboutlogins/content/aboutLogins.js | 6 ++++++
.../aboutlogins/content/components/fxaccounts-button.css | 5 +++++
.../components/aboutlogins/content/components/menu-button.css | 10 ++++++++++
5 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/browser/components/aboutlogins/AboutLoginsParent.jsm b/browser/components/aboutlogins/AboutLoginsParent.jsm
index db0b55d26abc..39fd2356ce99 100644
--- a/browser/components/aboutlogins/AboutLoginsParent.jsm
+++ b/browser/components/aboutlogins/AboutLoginsParent.jsm
@@ -61,6 +61,7 @@ XPCOMUtils.defineLazyGetter(this, "AboutLoginsL10n", () => {
const ABOUT_LOGINS_ORIGIN = "about:logins";
const MASTER_PASSWORD_NOTIFICATION_ID = "master-password-login-required";
+const NOCERTDB_PREF = "security.nocertdb";
// about:logins will always use the privileged content process,
// even if it is disabled for other consumers such as about:newtab.
@@ -273,6 +274,7 @@ class AboutLoginsParent extends JSWindowActorParent {
importVisible:
Services.policies.isAllowed("profileImport") &&
AppConstants.platform != "linux",
+ canCreateLogins: !Services.prefs.getBoolPref(NOCERTDB_PREF, false),
});
await AboutLogins._sendAllLoginRelatedObjects(
diff --git a/browser/components/aboutlogins/content/aboutLogins.css b/browser/components/aboutlogins/content/aboutLogins.css
index e3528ca49b84..eaa224178487 100644
--- a/browser/components/aboutlogins/content/aboutLogins.css
+++ b/browser/components/aboutlogins/content/aboutLogins.css
@@ -69,6 +69,11 @@ login-item {
grid-area: login;
}
+/* Do not promote Mozilla Sync in Tor Browser. */
+login-intro {
+ display: none !important;
+}
+
#branding-logo {
flex-basis: var(--sidebar-width);
flex-shrink: 0;
@@ -83,7 +88,8 @@ login-item {
}
}
-:root:not(.official-branding) #branding-logo {
+/* Hide "Firefox LockWise" branding in Tor Browser. */
+#branding-logo {
visibility: hidden;
}
diff --git a/browser/components/aboutlogins/content/aboutLogins.js b/browser/components/aboutlogins/content/aboutLogins.js
index 494ef5c7a15b..27ff0295f2f6 100644
--- a/browser/components/aboutlogins/content/aboutLogins.js
+++ b/browser/components/aboutlogins/content/aboutLogins.js
@@ -22,6 +22,9 @@ const gElements = {
".menuitem-remove-all-logins"
);
},
+ get createNewLoginButton() {
+ return this.loginList.shadowRoot.querySelector(".create-login-button");
+ },
};
let numberOfLogins = 0;
@@ -128,6 +131,9 @@ window.addEventListener("AboutLoginsChromeToContent", event => {
gElements.loginList.setSortDirection(event.detail.value.selectedSort);
document.documentElement.classList.add("initialized");
gElements.loginList.classList.add("initialized");
+ if (!event.detail.value.canCreateLogins) {
+ gElements.createNewLoginButton.disabled = true;
+ }
break;
}
case "ShowLoginItemError": {
diff --git a/browser/components/aboutlogins/content/components/fxaccounts-button.css b/browser/components/aboutlogins/content/components/fxaccounts-button.css
index c8925f6fc75d..55c2a8810fa1 100644
--- a/browser/components/aboutlogins/content/components/fxaccounts-button.css
+++ b/browser/components/aboutlogins/content/components/fxaccounts-button.css
@@ -8,6 +8,11 @@
align-items: center;
}
+/* Do not promote Mozilla Sync in Tor Browser. */
+.logged-out-view {
+ display: none !important;
+}
+
.fxaccounts-extra-text {
/* Only show at most 3 lines of text to limit the
text from overflowing the header. */
diff --git a/browser/components/aboutlogins/content/components/menu-button.css b/browser/components/aboutlogins/content/components/menu-button.css
index 6bc14f7892fa..22a06e3727ed 100644
--- a/browser/components/aboutlogins/content/components/menu-button.css
+++ b/browser/components/aboutlogins/content/components/menu-button.css
@@ -91,3 +91,13 @@
.menuitem-preferences {
background-image: url("chrome://global/skin/icons/settings.svg");
}
+
+/*
+ * Do not promote LockWise mobile apps in Tor Browser: hide the menu items
+ * and the separator line that precedes them.
+ */
+.menuitem-mobile-android,
+.menuitem-mobile-ios,
+button[data-event-name="AboutLoginsGetHelp"] + hr {
+ display: none !important;
+}
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 40198: Expose privacy.spoof_english pref in GeckoView
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 09a85920591d49d87a4a54f0567af8e8d1b64864
Author: Alex Catarineu <acat(a)torproject.org>
Date: Sun Oct 18 17:06:04 2020 +0200
Bug 40198: Expose privacy.spoof_english pref in GeckoView
---
mobile/android/geckoview/api.txt | 3 ++
.../mozilla/geckoview/GeckoRuntimeSettings.java | 33 ++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/mobile/android/geckoview/api.txt b/mobile/android/geckoview/api.txt
index f9f0c9d14a8a..3335aaffb1fb …
[View More]100644
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -737,6 +737,7 @@ package org.mozilla.geckoview {
method public boolean getRemoteDebuggingEnabled();
method @Nullable public GeckoRuntime getRuntime();
method @Nullable public Rect getScreenSizeOverride();
+ method public boolean getSpoofEnglish();
method @Nullable public RuntimeTelemetry.Delegate getTelemetryDelegate();
method public int getTorSecurityLevel();
method public boolean getUseMaxScreenDepth();
@@ -759,6 +760,7 @@ package org.mozilla.geckoview {
method @NonNull public GeckoRuntimeSettings setLoginAutofillEnabled(boolean);
method @NonNull public GeckoRuntimeSettings setPreferredColorScheme(int);
method @NonNull public GeckoRuntimeSettings setRemoteDebuggingEnabled(boolean);
+ method @NonNull public GeckoRuntimeSettings setSpoofEnglish(boolean);
method @NonNull public GeckoRuntimeSettings setTorSecurityLevel(int);
method @NonNull public GeckoRuntimeSettings setWebFontsEnabled(boolean);
method @NonNull public GeckoRuntimeSettings setWebManifestEnabled(boolean);
@@ -799,6 +801,7 @@ package org.mozilla.geckoview {
method @NonNull public GeckoRuntimeSettings.Builder preferredColorScheme(int);
method @NonNull public GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean);
method @NonNull public GeckoRuntimeSettings.Builder screenSizeOverride(int, int);
+ method @NonNull public GeckoRuntimeSettings.Builder spoofEnglish(boolean);
method @NonNull public GeckoRuntimeSettings.Builder telemetryDelegate(@NonNull RuntimeTelemetry.Delegate);
method @NonNull public GeckoRuntimeSettings.Builder torSecurityLevel(int);
method @NonNull public GeckoRuntimeSettings.Builder useMaxScreenDepth(boolean);
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
index d88e296d554a..5b54447cb6e6 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -483,6 +483,17 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
getSettings().mTorSecurityLevel.set(level);
return this;
}
+
+ /**
+ * Sets whether we should spoof locale to English for webpages.
+ *
+ * @param flag True if we should spoof locale to English for webpages, false otherwise.
+ * @return This Builder instance.
+ */
+ public @NonNull Builder spoofEnglish(final boolean flag) {
+ getSettings().mSpoofEnglish.set(flag ? 2 : 1);
+ return this;
+ }
}
private GeckoRuntime mRuntime;
@@ -541,6 +552,8 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
"dom.ipc.processCount", 2);
/* package */ final Pref<Integer> mTorSecurityLevel = new Pref<>(
"extensions.torbutton.security_slider", 4);
+ /* package */ final Pref<Integer> mSpoofEnglish = new Pref<>(
+ "privacy.spoof_english", 0);
/* package */ int mPreferredColorScheme = COLOR_SCHEME_SYSTEM;
@@ -1319,6 +1332,26 @@ public final class GeckoRuntimeSettings extends RuntimeSettings {
return this;
}
+ /**
+ * Get whether we should spoof locale to English for webpages.
+ *
+ * @return Whether we should spoof locale to English for webpages.
+ */
+ public boolean getSpoofEnglish() {
+ return mSpoofEnglish.get() == 2;
+ }
+
+ /**
+ * Set whether we should spoof locale to English for webpages.
+ *
+ * @param flag A flag determining whether we should locale to English for webpages.
+ * @return This GeckoRuntimeSettings instance.
+ */
+ public @NonNull GeckoRuntimeSettings setSpoofEnglish(final boolean flag) {
+ mSpoofEnglish.commit(flag ? 2 : 1);
+ return this;
+ }
+
@Override // Parcelable
public void writeToParcel(final Parcel out, final int flags) {
super.writeToParcel(out, flags);
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 40309: Avoid using regional OS locales
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 054ad0f4924f87235031043fe2c93fbc3e3de460
Author: Alex Catarineu <acat(a)torproject.org>
Date: Wed Jan 27 11:28:05 2021 +0100
Bug 40309: Avoid using regional OS locales
Only use regional OS locales if the pref
`intl.regional_prefs.use_os_locales` is set to true.
---
intl/locale/LocaleService.cpp | 25 -------------------------
1 file changed, 25 deletions(-)
diff --git a/intl/locale/LocaleService.cpp b/intl/locale/LocaleService.cpp
index 022d41cab2e2..…
[View More]ac001ee98991 100644
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -452,31 +452,6 @@ LocaleService::GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal) {
OSPreferences::GetInstance()->GetRegionalPrefsLocales(aRetVal))) {
return NS_OK;
}
-
- // If we fail to retrieve them, return the app locales.
- GetAppLocalesAsBCP47(aRetVal);
- return NS_OK;
- }
-
- // Otherwise, fetch OS Regional Preferences locales and compare the first one
- // to the app locale. If the language subtag matches, we can safely use
- // the OS Regional Preferences locale.
- //
- // This facilitates scenarios such as Firefox in "en-US" and User sets
- // regional prefs to "en-GB".
- nsAutoCString appLocale;
- AutoTArray<nsCString, 10> regionalPrefsLocales;
- LocaleService::GetInstance()->GetAppLocaleAsBCP47(appLocale);
-
- if (NS_FAILED(OSPreferences::GetInstance()->GetRegionalPrefsLocales(
- regionalPrefsLocales))) {
- GetAppLocalesAsBCP47(aRetVal);
- return NS_OK;
- }
-
- if (LocaleService::LanguagesMatch(appLocale, regionalPrefsLocales[0])) {
- aRetVal = regionalPrefsLocales.Clone();
- return NS_OK;
}
// Otherwise use the app locales.
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] 40209: Implement Basic Crypto Safety
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit c71ec8ab184f588fec602a293fc0f11adf2b251f
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 +++++++…
[View More]++++++++++++++++++++
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 3eac455c5356..00b0a3630f7c 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 f486d234b695..65d5ea9eae5b 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -441,6 +441,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 fbbfa44c11c9..786c1225bdd0 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",
[View Less]
1
0

[tor-browser/tor-browser-91.2.0esr-11.0-1] Bug 27476: Implement about:torconnect captive portal within Tor Browser
by sysrqb@torproject.org 06 Oct '21
by sysrqb@torproject.org 06 Oct '21
06 Oct '21
commit 22f5849135a232741d205eea8af8ba08682b45ae
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 …
[View More]through the TorProtocolService module
- the onion pattern from about:tor migrated to an .inc.xhtml file now used by both about:tor and about:torconnect
- various design tweaks and resusability fixes to onion pattern
- 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 | 8 +
.../alpha/content/identity-icons-brand.svg | 3 +
browser/branding/alpha/content/jar.mn | 2 +
browser/branding/alpha/content/tor-styles.css | 13 +
.../nightly/content/identity-icons-brand.svg | 3 +
browser/branding/nightly/content/jar.mn | 1 +
browser/branding/nightly/content/tor-styles.css | 13 +
.../official/content/identity-icons-brand.svg | 3 +
browser/branding/official/content/jar.mn | 1 +
browser/branding/official/content/tor-styles.css | 14 +
browser/branding/tor-styles.inc.css | 87 ++++
browser/components/BrowserGlue.jsm | 37 +-
browser/components/about/AboutRedirector.cpp | 4 +
browser/components/about/components.conf | 1 +
browser/components/moz.build | 1 +
.../onionservices/HttpsEverywhereControl.jsm | 17 +-
browser/components/sessionstore/SessionStore.jsm | 4 +
browser/components/torconnect/TorConnectChild.jsm | 9 +
browser/components/torconnect/TorConnectParent.jsm | 150 +++++++
.../torconnect/content/aboutTorConnect.css | 155 +++++++
.../torconnect/content/aboutTorConnect.js | 304 +++++++++++++
.../torconnect/content/aboutTorConnect.xhtml | 45 ++
.../components/torconnect/content/onion-slash.svg | 7 +
browser/components/torconnect/content/onion.svg | 3 +
.../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 +
.../components/torpreferences/content/torPane.js | 90 ++++
.../torpreferences/content/torPane.xhtml | 34 ++
.../torpreferences/content/torPreferences.css | 123 +++++
browser/components/urlbar/UrlbarInput.jsm | 31 ++
browser/modules/TorConnect.jsm | 499 +++++++++++++++++++++
browser/modules/TorProcessService.jsm | 12 +
browser/modules/TorProtocolService.jsm | 179 +++++++-
browser/modules/TorStrings.jsm | 80 ++++
browser/modules/moz.build | 2 +
.../shared/identity-block/identity-block.inc.css | 7 +-
browser/themes/shared/jar.inc.mn | 1 +
browser/themes/shared/onionPattern.css | 124 +++++
browser/themes/shared/onionPattern.inc.xhtml | 210 +++++++++
browser/themes/shared/urlbar-searchbar.inc.css | 2 +
dom/base/Document.cpp | 51 ++-
dom/base/nsGlobalWindowOuter.cpp | 2 +
.../processsingleton/MainProcessSingleton.jsm | 5 +
toolkit/modules/AsyncPrefs.jsm | 1 +
toolkit/modules/RemotePageAccessManager.jsm | 16 +
toolkit/mozapps/update/UpdateService.jsm | 68 ++-
.../lib/environments/browser-window.js | 4 +
56 files changed, 2643 insertions(+), 47 deletions(-)
diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm
index dfae068fd5c0..ba35962c943e 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 1b2c7bcb22cf..2111e67c5d09 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: (AppConstants.TOR_BROWSER_UPDATE ? /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|ion|tor|tbupdate)(?:[?#]|$)/i : /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|ion|tor)(?:[?#]|$)/i),
+ _secureInternalPages: (AppConstants.TOR_BROWSER_UPDATE ? /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|ion|tor|tbupdate)(?:[?#]|$)/i : /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|ion|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 fe35de9f6c63..c0f966efe0c7 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -81,6 +81,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",
OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UITour: "resource:///modules/UITour.jsm",
@@ -646,6 +647,7 @@ var gPageIcons = {
var gInitialPages = [
"about:tor",
+ "about:torconnect",
"about:blank",
"about:newtab",
"about:home",
@@ -1866,6 +1868,8 @@ var gBrowserInit = {
}
this._loadHandled = true;
+
+ TorBootstrapUrlbar.init();
},
_cancelDelayedStartup() {
@@ -2416,32 +2420,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;
})());
},
@@ -2508,6 +2528,8 @@ var gBrowserInit = {
OnionAuthPrompt.uninit();
+ TorBootstrapUrlbar.uninit();
+
gAccessibilityServiceIndicator.uninit();
if (gToolbarKeyNavEnabled) {
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index 65445a099148..394a46414018 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
@@ -113,6 +114,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 e5b223025a8b..60f602ba6530 100644
--- a/browser/base/content/certerror/aboutNetError.js
+++ b/browser/base/content/certerror/aboutNetError.js
@@ -240,7 +240,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
@@ -259,6 +259,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 1aad36ab3bfc..b881492864ae 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -331,6 +331,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 342a92a766a4..3d22093119ca 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,13 @@ 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
+ if (url === "about:tor" && TorConnect.shouldShowTorConnect) {
+ url = `about:torconnect?redirect=${encodeURIComponent("about:tor")}`;
+ }
+
var params;
if (arguments.length == 3 && typeof arguments[2] == "object") {
diff --git a/browser/branding/alpha/content/identity-icons-brand.svg b/browser/branding/alpha/content/identity-icons-brand.svg
new file mode 100644
index 000000000000..30cd52ba5c51
--- /dev/null
+++ b/browser/branding/alpha/content/identity-icons-brand.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623607,7.28735377 12.0246161,7.27371516 L12.0246161,5.81937131 C15.4272884,5.8326773 18.1819593,8.59400123 18.1819593,11.9996673 C18.1819593,15.4056661 15.4272884,18.1669901 12.0246161,18.1802961 L12.0246161,16.7259522 Z M12.0246161,9.45556355 C13.4187503,9.46886953 14.5454344,10.6022066 14.5454344,11.9996673 C14.5454344,13.3974608 13.4187503,14.5307978 12.0246161,14.5441038 L12.0246161,9.45556355 Z M0,11.9996673 C0,18.6273771 5.37229031,24 12,24 C18
.6273771,24 24,18.6273771 24,11.9996673 C24,5.37229031 18.6273771,0 12,0 C5.37229031,0 0,5.37229031 0,11.9996673 Z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/branding/alpha/content/jar.mn b/browser/branding/alpha/content/jar.mn
index 6db01f74fd20..93ff6ecf736b 100644
--- a/browser/branding/alpha/content/jar.mn
+++ b/browser/branding/alpha/content/jar.mn
@@ -18,4 +18,6 @@ browser.jar:
content/branding/icon128.png (../default128.png)
content/branding/icon256.png (../default256.png)
content/branding/icon512.png (../default512.png)
+ content/branding/identity-icons-brand.svg
content/branding/aboutDialog.css
+* content/branding/tor-styles.css
diff --git a/browser/branding/alpha/content/tor-styles.css b/browser/branding/alpha/content/tor-styles.css
new file mode 100644
index 000000000000..14c1915ef871
--- /dev/null
+++ b/browser/branding/alpha/content/tor-styles.css
@@ -0,0 +1,13 @@
+%include ../../tor-styles.inc.css
+
+/* default theme*/
+:root,
+/* light theme*/
+:root:-moz-lwtheme-darktext {
+ --tor-branding-color: var(--teal-70);
+}
+
+/* dark theme */
+:root:-moz-lwtheme-brighttext {
+ --tor-branding-color: var(--teal-60);
+}
\ No newline at end of file
diff --git a/browser/branding/nightly/content/identity-icons-brand.svg b/browser/branding/nightly/content/identity-icons-brand.svg
new file mode 100644
index 000000000000..30cd52ba5c51
--- /dev/null
+++ b/browser/branding/nightly/content/identity-icons-brand.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623607,7.28735377 12.0246161,7.27371516 L12.0246161,5.81937131 C15.4272884,5.8326773 18.1819593,8.59400123 18.1819593,11.9996673 C18.1819593,15.4056661 15.4272884,18.1669901 12.0246161,18.1802961 L12.0246161,16.7259522 Z M12.0246161,9.45556355 C13.4187503,9.46886953 14.5454344,10.6022066 14.5454344,11.9996673 C14.5454344,13.3974608 13.4187503,14.5307978 12.0246161,14.5441038 L12.0246161,9.45556355 Z M0,11.9996673 C0,18.6273771 5.37229031,24 12,24 C18
.6273771,24 24,18.6273771 24,11.9996673 C24,5.37229031 18.6273771,0 12,0 C5.37229031,0 0,5.37229031 0,11.9996673 Z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/branding/nightly/content/jar.mn b/browser/branding/nightly/content/jar.mn
index 6db01f74fd20..40dd91890a91 100644
--- a/browser/branding/nightly/content/jar.mn
+++ b/browser/branding/nightly/content/jar.mn
@@ -19,3 +19,4 @@ browser.jar:
content/branding/icon256.png (../default256.png)
content/branding/icon512.png (../default512.png)
content/branding/aboutDialog.css
+* content/branding/tor-styles.css
diff --git a/browser/branding/nightly/content/tor-styles.css b/browser/branding/nightly/content/tor-styles.css
new file mode 100644
index 000000000000..52e1761e5459
--- /dev/null
+++ b/browser/branding/nightly/content/tor-styles.css
@@ -0,0 +1,13 @@
+%include ../../tor-styles.inc.css
+
+/* default theme*/
+:root,
+/* light theme*/
+:root:-moz-lwtheme-darktext {
+ --tor-branding-color: var(--blue-60);
+}
+
+/* dark theme */
+:root:-moz-lwtheme-brighttext {
+ --tor-branding-color: var(--blue-40);
+}
\ No newline at end of file
diff --git a/browser/branding/official/content/identity-icons-brand.svg b/browser/branding/official/content/identity-icons-brand.svg
new file mode 100644
index 000000000000..30cd52ba5c51
--- /dev/null
+++ b/browser/branding/official/content/identity-icons-brand.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623607,7.28735377 12.0246161,7.27371516 L12.0246161,5.81937131 C15.4272884,5.8326773 18.1819593,8.59400123 18.1819593,11.9996673 C18.1819593,15.4056661 15.4272884,18.1669901 12.0246161,18.1802961 L12.0246161,16.7259522 Z M12.0246161,9.45556355 C13.4187503,9.46886953 14.5454344,10.6022066 14.5454344,11.9996673 C14.5454344,13.3974608 13.4187503,14.5307978 12.0246161,14.5441038 L12.0246161,9.45556355 Z M0,11.9996673 C0,18.6273771 5.37229031,24 12,24 C18
.6273771,24 24,18.6273771 24,11.9996673 C24,5.37229031 18.6273771,0 12,0 C5.37229031,0 0,5.37229031 0,11.9996673 Z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/branding/official/content/jar.mn b/browser/branding/official/content/jar.mn
index 6db01f74fd20..40dd91890a91 100644
--- a/browser/branding/official/content/jar.mn
+++ b/browser/branding/official/content/jar.mn
@@ -19,3 +19,4 @@ browser.jar:
content/branding/icon256.png (../default256.png)
content/branding/icon512.png (../default512.png)
content/branding/aboutDialog.css
+* content/branding/tor-styles.css
diff --git a/browser/branding/official/content/tor-styles.css b/browser/branding/official/content/tor-styles.css
new file mode 100644
index 000000000000..e4ccb5c767a9
--- /dev/null
+++ b/browser/branding/official/content/tor-styles.css
@@ -0,0 +1,14 @@
+%include ../../tor-styles.inc.css
+
+/* default theme*/
+:root,
+/* light theme*/
+:root:-moz-lwtheme-darktext {
+ --tor-branding-color: var(--purple-60);
+}
+
+/* dark theme */
+:root:-moz-lwtheme-brighttext {
+ --tor-branding-color: var(--purple-30);
+}
+
diff --git a/browser/branding/tor-styles.inc.css b/browser/branding/tor-styles.inc.css
new file mode 100644
index 000000000000..55dc9b6238b3
--- /dev/null
+++ b/browser/branding/tor-styles.inc.css
@@ -0,0 +1,87 @@
+:root {
+ /* photon colors, not all of them are available for whatever reason
+ in firefox, so here they are */
+
+ --magenta-50: #ff1ad9;
+ --magenta-60: #ed00b5;
+ --magenta-70: #b5007f;
+ --magenta-80: #7d004f;
+ --magenta-90: #440027;
+
+ --purple-30: #c069ff;
+ --purple-40: #ad3bff;
+ --purple-50: #9400ff;
+ --purple-60: #8000d7;
+ --purple-70: #6200a4;
+ --purple-80: #440071;
+ --purple-90: #25003e;
+
+ --blue-40: #45a1ff;
+ --blue-50: #0a84ff;
+ --blue-50-a30: rgba(10, 132, 255, 0.3);
+ --blue-60: #0060df;
+ --blue-70: #003eaa;
+ --blue-80: #002275;
+ --blue-90: #000f40;
+
+ --teal-50: #00feff;
+ --teal-60: #00c8d7;
+ --teal-70: #008ea4;
+ --teal-80: #005a71;
+ --teal-90: #002d3e;
+
+ --green-50: #30e60b;
+ --green-60: #12bc00;
+ --green-70: #058b00;
+ --green-80: #006504;
+ --green-90: #003706;
+
+ --yellow-50: #ffe900;
+ --yellow-60: #d7b600;
+ --yellow-70: #a47f00;
+ --yellow-80: #715100;
+ --yellow-90: #3e2800;
+
+ --red-50: #ff0039;
+ --red-60: #d70022;
+ --red-70: #a4000f;
+ --red-80: #5a0002;
+ --red-90: #3e0200;
+
+ --orange-50: #ff9400;
+ --orange-60: #d76e00;
+ --orange-70: #a44900;
+ --orange-80: #712b00;
+ --orange-90: #3e1300;
+
+ --grey-10: #f9f9fa;
+ --grey-10-a10: rgba(249, 249, 250, 0.1);
+ --grey-10-a20: rgba(249, 249, 250, 0.2);
+ --grey-10-a40: rgba(249, 249, 250, 0.4);
+ --grey-10-a60: rgba(249, 249, 250, 0.6);
+ --grey-10-a80: rgba(249, 249, 250, 0.8);
+ --grey-20: #ededf0;
+ --grey-30: #d7d7db;
+ --grey-40: #b1b1b3;
+ --grey-50: #737373;
+ --grey-60: #4a4a4f;
+ --grey-70: #38383d;
+ --grey-80: #2a2a2e;
+ --grey-90: #0c0c0d;
+ --grey-90-a05: rgba(12, 12, 13, 0.05);
+ --grey-90-a10: rgba(12, 12, 13, 0.1);
+ --grey-90-a20: rgba(12, 12, 13, 0.2);
+ --grey-90-a30: rgba(12, 12, 13, 0.3);
+ --grey-90-a40: rgba(12, 12, 13, 0.4);
+ --grey-90-a50: rgba(12, 12, 13, 0.5);
+ --grey-90-a60: rgba(12, 12, 13, 0.6);
+ --grey-90-a70: rgba(12, 12, 13, 0.7);
+ --grey-90-a80: rgba(12, 12, 13, 0.8);
+ --grey-90-a90: rgba(12, 12, 13, 0.9);
+
+ --ink-70: #363959;
+ --ink-80: #202340;
+ --ink-90: #0f1126;
+
+ --white-100: #ffffff;
+}
\ No newline at end of file
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 65d5ea9eae5b..5588d1463d94 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -725,6 +725,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",
@@ -2522,7 +2536,28 @@ BrowserGlue.prototype = {
{
task: () => {
- OnionAliasStore.init();
+ const { TorConnect, TorConnectTopics } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+ );
+ if (!TorConnect.shouldShowTorConnect) {
+ // we will take this path when the user is using the legacy tor launcher or
+ // when Tor Browser didn't launch its own tor.
+ OnionAliasStore.init();
+ } else {
+ // this path is taken when using about:torconnect, we wait to init
+ // after we are bootstrapped and connected to tor
+ const topic = TorConnectTopics.BootstrapComplete;
+ let bootstrapObserver = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === topic) {
+ OnionAliasStore.init();
+ // we only need to init once, so remove ourselves as an obvserver
+ Services.obs.removeObserver(this, topic);
+ }
+ }
+ };
+ Services.obs.addObserver(bootstrapObserver, topic);
+ }
},
},
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
index 323c1b6fb653..fd828a630c92 100644
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -128,6 +128,10 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
nsIAboutModule::HIDE_FROM_ABOUTABOUT},
#endif
+ {"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 67f178ee23ff..0916bb75e1d5 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 0ea2969e60b0..c30497374912 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -55,6 +55,7 @@ DIRS += [
"syncedtabs",
"uitour",
"urlbar",
+ "torconnect",
"torpreferences",
"translation",
]
diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm
index 525ed5233be7..d673de4cd6e5 100644
--- a/browser/components/onionservices/HttpsEverywhereControl.jsm
+++ b/browser/components/onionservices/HttpsEverywhereControl.jsm
@@ -41,6 +41,7 @@ const SECUREDROP_TOR_ONION_CHANNEL = {
class HttpsEverywhereControl {
constructor() {
this._extensionMessaging = null;
+ this._init();
}
async _sendMessage(type, object) {
@@ -61,7 +62,6 @@ class HttpsEverywhereControl {
* Installs the .tor.onion update channel in https-everywhere
*/
async installTorOnionUpdateChannel(retries = 5) {
- this._init();
// TODO: https-everywhere store is initialized asynchronously, so sending a message
// immediately results in a `store.get is undefined` error.
@@ -143,5 +143,20 @@ class HttpsEverywhereControl {
if (!this._extensionMessaging) {
this._extensionMessaging = new ExtensionMessaging();
}
+
+ // update all of the existing https-everywhere channels
+ setTimeout(async () => {
+ let pinnedChannels = await this._sendMessage("get_pinned_update_channels");
+ for(let channel of pinnedChannels.update_channels) {
+ this._sendMessage("update_update_channel", channel);
+ }
+
+ let storedChannels = await this._sendMessage("get_stored_update_channels");
+ for(let channel of storedChannels.update_channels) {
+ this._sendMessage("update_update_channel", channel);
+ }
+ }, 0);
+
+
}
}
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..792f2af10ea6
--- /dev/null
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -0,0 +1,150 @@
+// 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 TorLauncherPrefs = Object.freeze({
+ quickstart: "extensions.torlauncher.quickstart",
+});
+
+/*
+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,
+ Exit: false,
+ ErrorMessage: TorConnect.errorMessage,
+ ErrorDetails: TorConnect.errorDetails,
+ BootstrapProgress: TorConnect.bootstrapProgress,
+ BootstrapStatus: TorConnect.bootstrapStatus,
+ ShowCopyLog: TorConnect.logHasWarningOrError,
+ QuickStartEnabled: Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false),
+ };
+
+ // 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: {
+ // tells about:torconnect pages to close or redirect themselves
+ // this flag will only be set if an about:torconnect page
+ // reaches the Bootstrapped state, so if a user
+ // navigates to about:torconnect manually after bootstrap, the page
+ // will not auto-close on them
+ self.state.Exit = true;
+ 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 "nsPref:changed": {
+ if (aData === TorLauncherPrefs.quickstart) {
+ self.state.QuickStartEnabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+ }
+ 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.prefs.addObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
+ }
+
+ willDestroy() {
+ // stop observing all of our torconnect:.* topics
+ for (const key in TorConnectTopics) {
+ const topic = TorConnectTopics[key];
+ Services.obs.removeObserver(this.torConnectObserver, topic);
+ }
+ Services.prefs.removeObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
+ }
+
+ receiveMessage(message) {
+ switch (message.name) {
+ case "torconnect:set-quickstart":
+ Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, message.data);
+ 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..3bdc6ff22192
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.css
@@ -0,0 +1,155 @@
+
+/* 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: 50px;
+}
+
+/* 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;
+ }
+}
+
+/* override firefox's default blue border on hover */
+input[type="checkbox"]:not(:disabled):hover {
+ border-color: var(--purple-70);
+}
+
+/* fix checkbox visibility when dark mode enabled */
+input[type="checkbox"]:checked {
+ fill: var(--in-content-page-color);
+}
+
+#connectButton {
+ background-color: var(--purple-60);
+}
+
+#connectButton:hover {
+ background-color: var(--purple-70);
+}
+
+#connectButton:active {
+ background-color: var(--purple-80);
+}
+
+#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: 1;
+ 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..4eed3cf6a5c3
--- /dev/null
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -0,0 +1,304 @@
+// 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_AutoConfiguring(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_FatalError(state) {
+ // TODO: noop until this state is used
+ }
+
+ 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);
+
+ // only exit about:torconnect if TorConnectParent directs us to
+ if (state.Exit) {
+ if (this.redirect) {
+ // first try to forward to final destination
+ document.location = this.redirect;
+ } else {
+ // or else close the window
+ window.close();
+ }
+ }
+ }
+
+ 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);
+ }
+
+ 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..efb09700ec0b
--- /dev/null
+++ b/browser/components/torconnect/content/onion-slash.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <g fill-opacity="context-fill-opacity" fill="context-fill">
+ <path d="M3.409559 13.112147C3.409559 13.112147 8.200807 8.103115 8.200807 8.103115C8.200807 8.103115 8.200807 6.516403 8.200807 6.516403C8.620819 6.516403 9.009719 6.703075 9.274171 6.998639C9.274171 6.998639 10.160863 6.080835 10.160863 6.080835C9.663071 5.567487 8.978607 5.256367 8.200807 5.256367C8.200807 5.256367 8.200807 4.400787 8.200807 4.400787C9.196391 4.400787 10.098639 4.805243 10.736435 5.458595C10.736435 5.458595 11.623127 4.540791 11.623127 4.540791C10.751991 3.669655 9.538623 3.125195 8.200807 3.125195C8.200807 3.125195 8.200807 2.269615 8.200807 2.269615C9.756407 2.269615 11.172003 2.907411 12.214255 3.918551C12.214255 3.918551 13.100947 3.000747 13.100947 3.000747C11.825355 1.756267 10.098639 0.994023 8.185251 0.994023C4.311807 0.994023 1.185051 4.120779 1.185051 7.994223C1.185051 10.016503 2.040631 11.836555 3.409559 13.112147C3.409559 13.112147 3.409559 13.112147 3.409559 13.112147"/>
+ <path d="M14.205423 4.416343C14.205423 4.416343 13.287619 5.380815 13.287619 5.380815C13.692075 6.158615 13.909859 7.045307 13.909859 7.994223C13.909859 11.152091 11.358675 13.718831 8.200807 13.718831C8.200807 13.718831 8.200807 12.863251 8.200807 12.863251C10.891995 12.863251 13.069835 10.669855 13.069835 7.978667C13.069835 7.278647 12.929831 6.625295 12.665379 6.018611C12.665379 6.018611 11.685351 7.045307 11.685351 7.045307C11.763131 7.340871 11.809799 7.651991 11.809799 7.963111C11.809799 9.954279 10.207531 11.556547 8.216363 11.572103C8.216363 11.572103 8.216363 10.716523 8.216363 10.716523C9.725295 10.700967 10.954219 9.472043 10.954219 7.963111C10.954219 7.916443 10.954219 7.854219 10.954219 7.807551C10.954219 7.807551 4.887379 14.169955 4.887379 14.169955C5.867407 14.698859 6.987439 14.994423 8.185251 14.994423C12.058695 14.994423 15.185451 11.867667 15.185451 7.994223C15.185451 6.687519 14.827663 5.474151 14.205423 4.416343C14.205423 4.416343 14.205423 4.416343 14.20542
3 4.416343"/>
+ <path d="M1.791735 15.461103C1.402835 15.461103 1.045047 15.212207 0.889487 14.838863C0.733927 14.465519 0.827267 14.014395 1.107271 13.734387C1.107271 13.734387 13.458735 0.822907 13.458735 0.822907C13.847635 0.434007 14.454319 0.449563 14.827663 0.838467C15.201007 1.227367 15.216563 1.865163 14.843223 2.269619C14.843223 2.269619 2.491759 15.181099 2.491759 15.181099C2.289531 15.352215 2.040635 15.461107 1.791739 15.461107C1.791739 15.461107 1.791735 15.461103 1.791735 15.461103"/>
+ </g>
+</svg>
diff --git a/browser/components/torconnect/content/onion.svg b/browser/components/torconnect/content/onion.svg
new file mode 100644
index 000000000000..30cd52ba5c51
--- /dev/null
+++ b/browser/components/torconnect/content/onion.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.6023175 16.7272828,11.9996673 C16.7272828,9.39734991 14.623607,7.28735377 12.0246161,7.27371516 L12.0246161,5.81937131 C15.4272884,5.8326773 18.1819593,8.59400123 18.1819593,11.9996673 C18.1819593,15.4056661 15.4272884,18.1669901 12.0246161,18.1802961 L12.0246161,16.7259522 Z M12.0246161,9.45556355 C13.4187503,9.46886953 14.5454344,10.6022066 14.5454344,11.9996673 C14.5454344,13.3974608 13.4187503,14.5307978 12.0246161,14.5441038 L12.0246161,9.45556355 Z M0,11.9996673 C0,18.6273771 5.37229031,24 12,24 C18
.6273771,24 24,18.6273771 24,11.9996673 C24,5.37229031 18.6273771,0 12,0 C5.37229031,0 0,5.37229031 0,11.9996673 Z"/>
+</svg>
\ No newline at end of file
diff --git a/browser/components/torconnect/content/torBootstrapUrlbar.js b/browser/components/torconnect/content/torBootstrapUrlbar.js
new file mode 100644
index 000000000000..7843b80be8b9
--- /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", () => {
+ window.openTrustedLinkIn("about:torconnect", "tab");
+ });
+ 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/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
index 49054b5dac6a..59ecdec6d1d9 100644
--- a/browser/components/torpreferences/content/torPane.js
+++ b/browser/components/torpreferences/content/torPane.js
@@ -1,9 +1,15 @@
"use strict";
+/* global Services */
+
const { TorProtocolService } = ChromeUtils.import(
"resource:///modules/TorProtocolService.jsm"
);
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+
const {
TorBridgeSource,
TorBridgeSettings,
@@ -51,6 +57,10 @@ const { parsePort, parseBridgeStrings, parsePortList } = ChromeUtils.import(
"chrome://browser/content/torpreferences/parseFunctions.jsm"
);
+const TorLauncherPrefs = {
+ quickstart: "extensions.torlauncher.quickstart",
+}
+
/*
Tor Pane
@@ -62,11 +72,21 @@ const gTorPane = (function() {
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",
@@ -112,6 +132,10 @@ const gTorPane = (function() {
let retval = {
// cached frequently accessed DOM elements
+ _messageBox: null,
+ _messageBoxMessage: null,
+ _messageBoxButton: null,
+ _enableQuickstartCheckbox: null,
_useBridgeCheckbox: null,
_bridgeSelectionRadiogroup: null,
_builtinBridgeOption: null,
@@ -161,6 +185,43 @@ const gTorPane = (function() {
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();
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ // switch to existing about:torconnect tab or create a new one
+ win.switchToTabHavingURI("about:torconnect", true);
+ });
+
+ let populateMessagebox = () => {
+ if (TorConnect.shouldShowTorConnect) {
+ // set messagebox style and text
+ if (TorProtocolService.torBootstrapErrorOccurred()) {
+ this._messageBox.className = "error";
+ this._messageBoxMessage.innerText = TorStrings.torConnect.tryAgainMessage;
+ this._messageBoxButton.innerText = TorStrings.torConnect.tryAgain;
+ } else {
+ this._messageBox.className = "warning";
+ this._messageBoxMessage.innerText = TorStrings.torConnect.connectMessage;
+ this._messageBoxButton.innerText = TorStrings.torConnect.torConnectButton;
+ }
+ } else {
+ this._messageBox.className = "hidden";
+ this._messageBoxMessage.innerText = "";
+ this._messageBoxButton.innerText = "";
+ }
+ }
+ populateMessagebox();
+ // update the messagebox whenever we come back to the page
+ window.addEventListener("focus", val => {
+ populateMessagebox();
+ });
+
// Heading
prefpane.querySelector(selectors.torPreferences.header).innerText =
TorStrings.settings.torPreferencesHeading;
@@ -177,6 +238,26 @@ const gTorPane = (function() {
);
}
+ // 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;
+ Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, checked);
+ });
+ this._enableQuickstartCheckbox.checked = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+ Services.prefs.addObserver(TorLauncherPrefs.quickstart, this);
+
// Bridge setup
prefpane.querySelector(selectors.bridges.header).innerText =
TorStrings.settings.bridgesHeading;
@@ -537,6 +618,15 @@ const gTorPane = (function() {
// Callbacks
//
+ // callback for when the quickstart pref changes
+ observe(subject, topic, data) {
+ if (topic != "nsPref:changed") return;
+ if (data === TorLauncherPrefs.quickstart) {
+ this._enableQuickstartCheckbox.checked =
+ Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+ }
+ },
+
// callback when using bridges toggled
onToggleBridge(enabled) {
this._useBridgeCheckbox.checked = enabled;
diff --git a/browser/components/torpreferences/content/torPane.xhtml b/browser/components/torpreferences/content/torPane.xhtml
index 3c966b2b3726..7c8071f2cf10 100644
--- a/browser/components/torpreferences/content/torPane.xhtml
+++ b/browser/components/torpreferences/content/torPane.xhtml
@@ -3,6 +3,29 @@
<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"
@@ -18,6 +41,17 @@
</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"
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
index 4dac2c457823..47b8ff18e0af 100644
--- a/browser/components/torpreferences/content/torPreferences.css
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -1,7 +1,130 @@
+@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: 4px;
+}
+
+#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;
+ width: 100%;
+}
+
+#torPreferences-connectMessageBox td {
+ vertical-align: top;
+ padding: 0px;
+}
+
+#torPreferences-connectMessageBox td:first-child {
+ width: 24px;
+}
+
+#torPreferences-connectMessageBox-icon {
+ display: block;
+ width: 16px;
+ height: 16px;
+ padding: 4px;
+
+ mask-repeat: no-repeat !important;
+ mask-size: 16px !important;
+ mask-position: 4px 4px !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 {
+ display: block;
+ line-height: 16px;
+ font-size: 13px;
+ margin-right: 8px;
+ padding-left: 4px!important;
+ padding-top: 4px!important;
+}
+
+#torPreferences-connectMessageBox-button {
+ display: block;
+ width: auto;
+ height: 24px;
+ line-height: 24px;
+ min-height: 24px;
+ max-height: 24px;
+ margin: 0px;
+
+ border-radius: 2px;
+ border: 0;
+ padding-left: 8px;
+ padding-right: 8px;
+ margin-left: auto;
+ margin-right: 0px;
+
+ font-size: 11px;
+ font-weight: 400;
+ white-space: nowrap;
+}
+
+#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);
+ color: white!important;
+}
+
+#torPreferences-connectMessageBox.warning #torPreferences-connectMessageBox-button:active {
+ background-color: var(--purple-90);
+ color: white!important;
+}
+
+/* Advanced Settings */
+
#torPreferences-advanced-grid {
display: grid;
grid-template-columns: auto 1fr;
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index 27261544127c..a118a6f76c19 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -10,6 +10,33 @@ 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
+ }
+
+ return openUILinkWhere;
+};
+
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.jsm",
@@ -2416,6 +2443,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/TorConnect.jsm b/browser/modules/TorConnect.jsm
new file mode 100644
index 000000000000..bd5c998a6063
--- /dev/null
+++ b/browser/modules/TorConnect.jsm
@@ -0,0 +1,499 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState"];
+
+const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+);
+
+const { BrowserWindowTracker } = ChromeUtils.import(
+ "resource:///modules/BrowserWindowTracker.jsm"
+);
+
+const { TorProtocolService, TorProcessStatus } = ChromeUtils.import(
+ "resource:///modules/TorProtocolService.jsm"
+);
+
+const { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+);
+
+/* Browser observer topis */
+const BrowserTopics = Object.freeze({
+ ProfileAfterChange: "profile-after-change",
+});
+
+/* tor-launcher observer topics */
+const TorTopics = Object.freeze({
+ ProcessIsReady: "TorProcessIsReady",
+ BootstrapStatus: "TorBootstrapStatus",
+ BootstrapError: "TorBootstrapError",
+ ProcessExited: "TorProcessExited",
+ LogHasWarnOrErr: "TorLogHasWarnOrErr",
+});
+
+/* Relevant prefs used by tor-launcher */
+const TorLauncherPrefs = Object.freeze({
+ quickstart: "extensions.torlauncher.quickstart",
+ prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
+});
+
+const TorConnectState = Object.freeze({
+ /* Our initial state */
+ Initial: "Initial",
+ /* In-between initial boot and bootstrapping, users can change tor network settings during this state */
+ Configuring: "Configuring",
+ /* Geo-location and setting bridges/etc */
+ AutoConfiguring: "AutoConfiguring",
+ /* Tor is bootstrapping */
+ Bootstrapping: "Bootstrapping",
+ /* Passthrough state back to Configuring or Fatal */
+ Error: "Error",
+ /* An unrecoverable error */
+ FatalError: "FatalError",
+ /* Final state, after successful bootstrap */
+ Bootstrapped: "Bootstrapped",
+ /* If we are using System tor or the legacy Tor-Launcher */
+ Disabled: "Disabled",
+});
+
+/*
+
+ TorConnect State Transitions
+
+ ┌──────────────────────┐
+ │ Disabled │
+ └──────────────────────┘
+ ▲
+ │ legacyOrSystemTor()
+ │
+ ┌──────────────────────┐
+ ┌────────────────────── │ Initial │ ───────────────────────────┐
+ │ └──────────────────────┘ │
+ │ │ │
+ │ │ beginBootstrap() │
+ │ ▼ │
+┌────────────────┐ │ bootstrapComplete() ┌────────────────────────────────────────────────┐ │ beginBootstrap()
+│ Bootstrapped │ ◀──┼────────────────────── │ Bootstrapping │ ◀┼─────────────────┐
+└────────────────┘ │ └────────────────────────────────────────────────┘ │ │
+ │ │ ▲ │ │ │
+ │ │ cancelBootstrap() │ beginBootstrap() └────┼─────────────┐ │
+ │ ▼ │ │ │ │
+ │ beginConfigure() ┌────────────────────────────────────────────────┐ │ │ │
+ └─────────────────────▶ │ │ │ │ │
+ │ │ │ │ │
+ beginConfigure() │ │ │ │ │
+ ┌──────────────────────────▶ │ Configuring │ │ │ │
+ │ │ │ │ │ │
+ │ │ │ │ │ │
+ │ ┌─────────────────────▶ │ │ │ │ │
+ │ │ └────────────────────────────────────────────────┘ │ │ │
+ │ │ │ │ │ │ │
+ │ │ cancelAutoconfigure() │ autoConfigure() │ ┌────┼─────────────┼───┘
+ │ │ ▼ │ │ │ │
+ │ │ ┌──────────────────────┐ │ │ │ │
+ │ └────────────────────── │ AutoConfiguring │ ─┼────────────────────┘ │ │
+ │ └──────────────────────┘ │ │ │
+ │ │ │ │ onError() │
+ │ │ onError() │ onError() │ │
+ │ ▼ ▼ │ │
+ │ ┌────────────────────────────────────────────────┐ │ │
+ └─────────────────────────── │ Error │ ◀┘ │
+ └────────────────────────────────────────────────┘ │
+ │ ▲ onError() │
+ │ onFatalError() └──────────────────┘
+ ▼
+ ┌──────────────────────┐
+ │ FatalError │
+ └──────────────────────┘
+
+*/
+
+
+/* Maps allowed state transitions
+ TorConnectStateTransitions[state] maps to an array of allowed states to transition to
+*/
+const TorConnectStateTransitions =
+ Object.freeze(new Map([
+ [TorConnectState.Initial,
+ [TorConnectState.Disabled,
+ TorConnectState.Bootstrapping,
+ TorConnectState.Configuring,
+ TorConnectState.Error]],
+ [TorConnectState.Configuring,
+ [TorConnectState.AutoConfiguring,
+ TorConnectState.Bootstrapping,
+ TorConnectState.Error]],
+ [TorConnectState.AutoConfiguring,
+ [TorConnectState.Configuring,
+ TorConnectState.Bootstrapping,
+ TorConnectState.Error]],
+ [TorConnectState.Bootstrapping,
+ [TorConnectState.Configuring,
+ TorConnectState.Bootstrapped,
+ TorConnectState.Error]],
+ [TorConnectState.Error,
+ [TorConnectState.Configuring,
+ TorConnectState.FatalError]],
+ // terminal states
+ [TorConnectState.FatalError, []],
+ [TorConnectState.Bootstrapped, []],
+ [TorConnectState.Disabled, []],
+ ]));
+
+/* Topics Notified by the TorConnect module */
+const TorConnectTopics = Object.freeze({
+ StateChange: "torconnect:state-change",
+ BootstrapProgress: "torconnect:bootstrap-progress",
+ BootstrapComplete: "torconnect:bootstrap-complete",
+ BootstrapError: "torconnect:bootstrap-error",
+ FatalError: "torconnect:fatal-error",
+});
+
+const TorConnect = (() => {
+ let retval = {
+
+ _state: TorConnectState.Initial,
+ _bootstrapProgress: 0,
+ _bootstrapStatus: null,
+ _errorMessage: null,
+ _errorDetails: null,
+ _logHasWarningOrError: false,
+
+ /* These functions are called after transitioning to a new state */
+ _transitionCallbacks: Object.freeze(new Map([
+ /* Initial is never transitioned to */
+ [TorConnectState.Initial, null],
+ /* Configuring */
+ [TorConnectState.Configuring, (self, prevState) => {
+ // TODO move this to the transition function
+ if (prevState === TorConnectState.Bootstrapping) {
+ TorProtocolService.torStopBootstrap();
+ }
+ }],
+ /* AutoConfiguring */
+ [TorConnectState.AutoConfiguring, (self, prevState) => {
+
+ }],
+ /* Bootstrapping */
+ [TorConnectState.Bootstrapping, (self, prevState) => {
+ let error = TorProtocolService.connect();
+ if (error) {
+ self.onError(error.message, error.details);
+ } else {
+ self._errorMessage = self._errorDetails = null;
+ }
+ }],
+ /* Bootstrapped */
+ [TorConnectState.Bootstrapped, (self,prevState) => {
+ // notify observers of bootstrap completion
+ Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
+ }],
+ /* Error */
+ [TorConnectState.Error, (self, prevState, errorMessage, errorDetails, fatal) => {
+ self._errorMessage = errorMessage;
+ self._errorDetails = errorDetails;
+
+ Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError);
+ if (fatal) {
+ self.onFatalError();
+ } else {
+ self.beginConfigure();
+ }
+ }],
+ /* FatalError */
+ [TorConnectState.FatalError, (self, prevState) => {
+ Services.obs.notifyObservers(null, TorConnectTopics.FatalError);
+ }],
+ /* Disabled */
+ [TorConnectState.Disabled, (self, prevState) => {
+
+ }],
+ ])),
+
+ _changeState: function(newState, ...args) {
+ const prevState = this._state;
+
+ // ensure this is a valid state transition
+ if (!TorConnectStateTransitions.get(prevState)?.includes(newState)) {
+ throw Error(`TorConnect: Attempted invalid state transition from ${prevState} to ${newState}`);
+ }
+
+ console.log(`TorConnect: transitioning state from ${prevState} to ${newState}`);
+
+ // set our new state first so that state transitions can themselves trigger
+ // a state transition
+ this._state = newState;
+
+ // call our transition function and forward any args
+ this._transitionCallbacks.get(newState)(this, prevState, ...args);
+
+ Services.obs.notifyObservers({state: newState}, TorConnectTopics.StateChange);
+ },
+
+ // init should be called on app-startup in MainProcessingSingleton.jsm
+ init : function() {
+ console.log("TorConnect: Init");
+
+ // delay remaining init until after profile-after-change
+ Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
+ },
+
+ observe: function(subject, topic, data) {
+ console.log(`TorConnect: observed ${topic}`);
+
+ switch(topic) {
+
+ /* Determine which state to move to from Initial */
+ case BrowserTopics.ProfileAfterChange: {
+ if (TorLauncherUtil.useLegacyLauncher || !TorProtocolService.ownsTorDaemon) {
+ // Disabled
+ this.legacyOrSystemTor();
+ } else {
+ // register the Tor topics we always care about
+ for (const topicKey in TorTopics) {
+ const topic = TorTopics[topicKey];
+ Services.obs.addObserver(this, topic);
+ console.log(`TorConnect: observing topic '${topic}'`);
+ }
+
+ if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) {
+ if (this.shouldQuickStart) {
+ // Quickstart
+ this.beginBootstrap();
+ } else {
+ // Configuring
+ this.beginConfigure();
+ }
+ }
+ }
+
+ Services.obs.removeObserver(this, topic);
+ break;
+ }
+ /* Transition out of Initial if Tor daemon wasn't running yet in BrowserTopics.ProfileAfterChange */
+ case TorTopics.ProcessIsReady: {
+ if (this.state === TorConnectState.Initial)
+ {
+ if (this.shouldQuickStart) {
+ // Quickstart
+ this.beginBootstrap();
+ } else {
+ // Configuring
+ this.beginConfigure();
+ }
+ }
+ break;
+ }
+ /* Updates our bootstrap status */
+ case TorTopics.BootstrapStatus: {
+ if (this._state != TorConnectState.Bootstrapping) {
+ console.log(`TorConnect: observed ${TorTopics.BootstrapStatus} topic while in state TorConnectState.${this._state}`);
+ break;
+ }
+
+ const obj = subject?.wrappedJSObject;
+ if (obj) {
+ this._bootstrapProgress= obj.PROGRESS;
+ this._bootstrapStatus = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG");
+
+ console.log(`TorConnect: Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`);
+ Services.obs.notifyObservers({
+ progress: this._bootstrapProgress,
+ status: this._bootstrapStatus,
+ hasWarnings: this._logHasWarningOrError
+ }, TorConnectTopics.BootstrapProgress);
+
+ if (this._bootstrapProgress === 100) {
+ this.bootstrapComplete();
+ }
+ }
+ break;
+ }
+ /* Handle bootstrap error*/
+ case TorTopics.BootstrapError: {
+ const obj = subject?.wrappedJSObject;
+ TorProtocolService.torStopBootstrap();
+ this.onError(obj.message, obj.details);
+ break;
+ }
+ case TorTopics.LogHasWarnOrErr: {
+ this._logHasWarningOrError = true;
+ break;
+ }
+ default:
+ // ignore
+ break;
+ }
+ },
+
+ /*
+ Various getters
+ */
+
+ get shouldShowTorConnect() {
+ // TorBrowser must control the daemon
+ return (TorProtocolService.ownsTorDaemon &&
+ // and we're not using the legacy launcher
+ !TorLauncherUtil.useLegacyLauncher &&
+ // legacy checks, TODO: maybe this should be in terms of our own state?
+ (TorProtocolService.isNetworkDisabled() || !TorProtocolService.isBootstrapDone()));
+ },
+
+ get shouldQuickStart() {
+ // quickstart must be enabled
+ return Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false) &&
+ // and the previous bootstrap attempt must have succeeded
+ !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true);
+ },
+
+ get state() {
+ return this._state;
+ },
+
+ get bootstrapProgress() {
+ return this._bootstrapProgress;
+ },
+
+ get bootstrapStatus() {
+ return this._bootstrapStatus;
+ },
+
+ get errorMessage() {
+ return this._errorMessage;
+ },
+
+ get errorDetails() {
+ return this._errorDetails;
+ },
+
+ get logHasWarningOrError() {
+ return this._logHasWarningOrError;
+ },
+
+ /*
+ These functions tell TorConnect to transition states
+ */
+
+ legacyOrSystemTor: function() {
+ console.log("TorConnect: legacyOrSystemTor()");
+ this._changeState(TorConnectState.Disabled);
+ },
+
+ beginBootstrap: function() {
+ console.log("TorConnect: beginBootstrap()");
+ this._changeState(TorConnectState.Bootstrapping);
+ },
+
+ beginConfigure: function() {
+ console.log("TorConnect: beginConfigure()");
+ this._changeState(TorConnectState.Configuring);
+ },
+
+ autoConfigure: function() {
+ console.log("TorConnect: autoConfigure()");
+ // TODO: implement
+ throw Error("TorConnect: not implemented");
+ },
+
+ cancelAutoConfigure: function() {
+ console.log("TorConnect: cancelAutoConfigure()");
+ // TODO: implement
+ throw Error("TorConnect: not implemented");
+ },
+
+ cancelBootstrap: function() {
+ console.log("TorConnect: cancelBootstrap()");
+ this._changeState(TorConnectState.Configuring);
+ },
+
+ bootstrapComplete: function() {
+ console.log("TorConnect: bootstrapComplete()");
+ this._changeState(TorConnectState.Bootstrapped);
+ },
+
+ onError: function(message, details) {
+ console.log("TorConnect: onError()");
+ this._changeState(TorConnectState.Error, message, details, false);
+ },
+
+ onFatalError: function() {
+ console.log("TorConnect: onFatalError()");
+ // TODO: implement
+ throw Error("TorConnect: not implemented");
+ },
+
+ /*
+ Further external commands and helper methods
+ */
+ openTorPreferences: function() {
+ const win = BrowserWindowTracker.getTopWindow()
+ win.openTrustedLinkIn("about:preferences#tor", "tab");
+ },
+
+ copyTorLogs: function() {
+ // Copy tor log messages to the system clipboard.
+ const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ const countObj = { value: 0 };
+ chSvc.copyString(TorProtocolService.getLog(countObj));
+ const count = countObj.value;
+ return TorLauncherUtil.getFormattedLocalizedString(
+ "copiedNLogMessagesShort",
+ [count],
+ 1
+ );
+ },
+
+ // called from browser.js on browser startup, passed in either the user's homepage(s)
+ // or uris passed via command-line; we want to replace them with about:torconnect uris
+ // which redirect after bootstrapping
+ getURIsToLoad: function(uriVariant) {
+ // convert the object we get from browser.js
+ let uriStrings = ((v) => {
+ // an interop array
+ if (v instanceof Ci.nsIArray) {
+ // Transform the nsIArray of nsISupportsString's into a JS Array of
+ // JS strings.
+ return Array.from(
+ v.enumerate(Ci.nsISupportsString),
+ supportStr => supportStr.data
+ );
+ // an interop string
+ } else if (v instanceof Ci.nsISupportsString) {
+ return [v.data];
+ // a js string
+ } else if (typeof v === "string") {
+ return v.split("|");
+ // a js array of js strings
+ } else if (Array.isArray(v) &&
+ v.reduce((allStrings, entry) => {return allStrings && (typeof entry === "string");}, true)) {
+ return v;
+ }
+ // about:tor as safe fallback
+ console.log(`TorConnect: getURIsToLoad() received unknown variant '${JSON.stringify(v)}'`);
+ return ["about:tor"];
+ })(uriVariant);
+
+ // will attempt to convert user-supplied string to a uri, fallback to about:tor if cannot convert
+ // to valid uri object
+ let uriStringToUri = (uriString) => {
+ let uri = Services.uriFixup.createFixupURI(uriString, 0);
+ return uri ? uri : Services.io.newURI("about:tor");
+ };
+ let uris = uriStrings.map(uriStringToUri);
+
+ // assume we have a valid uri and generate an about:torconnect redirect uri
+ let uriToRedirectUri = (uri) => {
+ return`about:torconnect?redirect=${encodeURIComponent(uri.spec)}`;
+ };
+ let redirectUris = uris.map(uriToRedirectUri);
+
+ console.log(`TorConnect: will load after bootstrap => [${uris.map((uri) => {return uri.spec;}).join(", ")}]`);
+ return redirectUris;
+ },
+ };
+ retval.init();
+ return retval;
+})(); /* TorConnect */
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/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm
index b4e6ed9a3253..e6c78b9a0eb1 100644
--- a/browser/modules/TorProtocolService.jsm
+++ b/browser/modules/TorProtocolService.jsm
@@ -1,21 +1,60 @@
+// Copyright (c) 2021, The Tor Project, Inc.
+
"use strict";
-var EXPORTED_SYMBOLS = ["TorProtocolService"];
+var EXPORTED_SYMBOLS = ["TorProtocolService", "TorProcessStatus"];
-const { TorLauncherUtil } = ChromeUtils.import(
- "resource://torlauncher/modules/tl-util.jsm"
+const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
);
+// see tl-process.js
+const TorProcessStatus = Object.freeze({
+ Unknown: 0,
+ Starting: 1,
+ Running: 2,
+ Exited: 3,
+});
+
+/* Browser observer topis */
+const BrowserTopics = Object.freeze({
+ ProfileAfterChange: "profile-after-change",
+});
+
var TorProtocolService = {
- _tlps: Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
- Ci.nsISupports
- ).wrappedJSObject,
+ _TorLauncherUtil: function() {
+ let { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+ );
+ return TorLauncherUtil;
+ }(),
+ _TorLauncherProtocolService: null,
+ _TorProcessService: null,
// maintain a map of tor settings set by Tor Browser so that we don't
// repeatedly set the same key/values over and over
// this map contains string keys to primitive or array values
_settingsCache: new Map(),
+ init() {
+ Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
+ },
+
+ observe(subject, topic, data) {
+ if (topic === BrowserTopics.ProfileAfterChange) {
+ // we have to delay init'ing this or else the crypto service inits too early without a profile
+ // which breaks the password manager
+ this._TorLauncherProtocolService = Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ this._TorProcessService = Cc["@torproject.org/torlauncher-process-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject,
+
+ Services.obs.removeObserver(this, topic);
+ }
+ },
+
_typeof(aValue) {
switch (typeof aValue) {
case "boolean":
@@ -118,7 +157,7 @@ var TorProtocolService = {
}
let errorObject = {};
- if (!this._tlps.TorSetConfWithReply(settingsObject, errorObject)) {
+ if (!this._TorLauncherProtocolService.TorSetConfWithReply(settingsObject, errorObject)) {
throw new Error(errorObject.details);
}
@@ -131,8 +170,8 @@ var TorProtocolService = {
_readSetting(aSetting) {
this._assertValidSettingKey(aSetting);
- let reply = this._tlps.TorGetConf(aSetting);
- if (this._tlps.TorCommandSucceeded(reply)) {
+ let reply = this._TorLauncherProtocolService.TorGetConf(aSetting);
+ if (this._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
return reply.lineArray;
}
throw new Error(reply.lineArray.join("\n"));
@@ -196,17 +235,129 @@ var TorProtocolService = {
// writes current tor settings to disk
flushSettings() {
- this._tlps.TorSendCommand("SAVECONF");
+ this.sendCommand("SAVECONF");
},
- getLog() {
- let countObj = { value: 0 };
- let torLog = this._tlps.TorGetLog(countObj);
+ getLog(countObj) {
+ countObj = countObj || { value: 0 };
+ let torLog = this._TorLauncherProtocolService.TorGetLog(countObj);
return torLog;
},
// true if we launched and control tor, false if using system tor
get ownsTorDaemon() {
- return TorLauncherUtil.shouldStartAndOwnTor;
+ return this._TorLauncherUtil.shouldStartAndOwnTor;
+ },
+
+ // Assumes `ownsTorDaemon` is true
+ isNetworkDisabled() {
+ const reply = TorProtocolService._TorLauncherProtocolService.TorGetConfBool(
+ "DisableNetwork",
+ true
+ );
+ if (TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
+ return reply.retVal;
+ }
+ return true;
+ },
+
+ enableNetwork() {
+ let settings = {};
+ settings.DisableNetwork = false;
+ let errorObject = {};
+ if (!this._TorLauncherProtocolService.TorSetConfWithReply(settings, errorObject)) {
+ throw new Error(errorObject.details);
+ }
+ },
+
+ sendCommand(cmd) {
+ return this._TorLauncherProtocolService.TorSendCommand(cmd);
+ },
+
+ retrieveBootstrapStatus() {
+ return this._TorLauncherProtocolService.TorRetrieveBootstrapStatus();
+ },
+
+ _GetSaveSettingsErrorMessage(aDetails) {
+ try {
+ return this._TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
+ } catch (e) {
+ console.log("GetSaveSettingsErrorMessage error", e);
+ return "Unexpected Error";
+ }
+ },
+
+ setConfWithReply(settings) {
+ let result = false;
+ const error = {};
+ try {
+ result = this._TorLauncherProtocolService.TorSetConfWithReply(settings, error);
+ } catch (e) {
+ console.log("TorSetConfWithReply error", e);
+ error.details = this._GetSaveSettingsErrorMessage(e.message);
+ }
+ return { result, error };
+ },
+
+ isBootstrapDone() {
+ return this._TorProcessService.mIsBootstrapDone;
+ },
+
+ clearBootstrapError() {
+ return this._TorProcessService.TorClearBootstrapError();
+ },
+
+ torBootstrapErrorOccurred() {
+ return this._TorProcessService.TorBootstrapErrorOccurred;
+ },
+
+ // Resolves to null if ok, or an error otherwise
+ connect() {
+ const kTorConfKeyDisableNetwork = "DisableNetwork";
+ const settings = {};
+ settings[kTorConfKeyDisableNetwork] = false;
+ const { result, error } = this.setConfWithReply(settings);
+ if (!result) {
+ return error;
+ }
+ try {
+ this.sendCommand("SAVECONF");
+ this.clearBootstrapError();
+ this.retrieveBootstrapStatus();
+ } catch (e) {
+ return error;
+ }
+ return null;
+ },
+
+ torLogHasWarnOrErr() {
+ return this._TorLauncherProtocolService.TorLogHasWarnOrErr;
+ },
+
+ torStopBootstrap() {
+ // Tell tor to disable use of the network; this should stop the bootstrap
+ // process.
+ const kErrorPrefix = "Setting DisableNetwork=1 failed: ";
+ try {
+ let settings = {};
+ settings.DisableNetwork = true;
+ const { result, error } = this.setConfWithReply(settings);
+ if (!result) {
+ console.log(
+ `Error stopping bootstrap ${kErrorPrefix} ${error.details}`
+ );
+ }
+ } catch (e) {
+ console.log(`Error stopping bootstrap ${kErrorPrefix} ${e}`);
+ }
+ this.retrieveBootstrapStatus();
+ },
+
+ get torProcessStatus() {
+ if (this._TorProcessService) {
+ return this._TorProcessService.TorProcessStatus;
+ }
+ return TorProcessStatus.Unknown;
},
};
+TorProtocolService.init();
\ No newline at end of file
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index 1e08b168e4af..c0691ff078ce 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 to the Tor Network"),
+ 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 1f7c6bc4c67e..1ea57aba1a93 100644
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -153,6 +153,8 @@ EXTRA_JS_MODULES += [
"TabsList.jsm",
"TabUnloader.jsm",
"ThemeVariableMap.jsm",
+ 'TorConnect.jsm',
+ 'TorProcessService.jsm',
"TorProtocolService.jsm",
"TorStrings.jsm",
"TransientPrefs.jsm",
diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css
index 71c4758ee1c3..283fc2113e2f 100644
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -53,16 +53,15 @@
border-radius: var(--urlbar-icon-border-radius);
}
-%ifdef MOZ_OFFICIAL_BRANDING
#identity-box[pageproxystate="valid"].notSecureText #identity-icon-label,
#identity-box[pageproxystate="valid"].chromeUI #identity-icon-label {
- color: #420C5D;
+ color: var(--tor-branding-color);
+ opacity: 1;
}
toolbar[brighttext] #identity-box[pageproxystate="valid"].chromeUI #identity-icon-label {
color: #CC80FF;
}
-%endif
#identity-box[pageproxystate="valid"].chromeUI #identity-icon-label,
#identity-box[pageproxystate="valid"].extensionPage #identity-icon-label,
@@ -161,6 +160,8 @@ toolbar[brighttext] #identity-box[pageproxystate="valid"].chromeUI #identity-ico
#identity-box[pageproxystate="valid"].chromeUI #identity-icon {
list-style-image: url(chrome://branding/content/icon16.png);
+ fill: var(--tor-branding-color);
+ fill-opacity: 1;
}
@media (min-resolution: 1.1dppx) {
#identity-box[pageproxystate="valid"].chromeUI #identity-icon {
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index 3b11a9864cf8..114688e3b128 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -9,6 +9,7 @@
skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css)
skin/classic/browser/offlineSupportPages.css (../shared/offlineSupportPages.css)
+ skin/classic/browser/onionPattern.css (../shared/onionPattern.css)
skin/classic/browser/blockedSite.css (../shared/blockedSite.css)
skin/classic/browser/error-pages.css (../shared/error-pages.css)
skin/classic/browser/aboutRestartRequired.css (../shared/aboutRestartRequired.css)
diff --git a/browser/themes/shared/onionPattern.css b/browser/themes/shared/onionPattern.css
new file mode 100644
index 000000000000..c605a4b4f59e
--- /dev/null
+++ b/browser/themes/shared/onionPattern.css
@@ -0,0 +1,124 @@
+/* Onion pattern */
+
+:root {
+ --sqrt3: 1.73205080757;
+}
+
+.onion-pattern-container {
+ opacity: var(--onion-opacity, 1);
+ flex: auto; /* grow to consume remaining space on the page */
+ display: flex;
+ margin: 0 auto;
+ width: 100%;
+ height: calc((2 + var(--sqrt3)) * var(--onion-radius, 50px)); /* room for 2 rows of circles */
+ max-height: calc((2 + var(--sqrt3)) * var(--onion-radius, 50px));
+ direction: ltr;
+}
+
+.onion-pattern-crop {
+ display: flex;
+ justify-content: center;
+ overflow-x: hidden;
+ pointer-events: none; /* for some reason, elements with overflow-x: hidden set become focusable */
+
+ margin: 0 auto;
+}
+
+/* Centers horizontally within the root container*/
+.onion-pattern-column {
+ width: calc(40 * var(--onion-radius, 50px)); /* room for 20 circles in a row */
+ height: calc((2 + var(--sqrt3)) * var(--onion-radius, 50px)); /* room for 2 rows of circles */
+ flex-shrink: 0;
+ overflow-x: hidden; /* clip extra circles on the sides */
+ pointer-events: none; /* for some reason, elements with overflow-x: hidden set become focusable */
+}
+
+.onion-pattern-row {
+ width: calc(40 * var(--onion-radius, 50px)); /* room for 20 circles in a row */
+ display: flex;
+ flex-direction: row;
+ position: relative;
+}
+
+.onion-pattern-offset-row {
+ left: calc(-1 * var(--onion-radius, 50px));
+ margin-top: calc((var(--sqrt3) - 2.0) * var(--onion-radius, 50px));
+}
+
+/* With borders, circles are 100x100 pixels*/
+.circle {
+ position: relative;
+ min-width: calc(2 * var(--onion-radius, 50px));
+ min-height: calc(2 * var(--onion-radius, 50px));
+ border-radius: 50%;
+ box-sizing: border-box;
+}
+
+.inner {
+ position: absolute;
+ box-sizing: border-box;
+ border-radius: 50%;
+}
+
+.inner:nth-child(1){
+ width: 100%;
+ height: 100%;
+}
+
+.inner:nth-child(2){
+ transform: translate(20%, 20%);
+ width: calc(100% * 5/7);
+ height: calc(100% * 5/7);
+}
+
+.inner:nth-child(3){
+ transform: translate(calc(100% * 2/3), calc(100% * 2/3));
+ width: calc(100% * 3/7);
+ height: calc(100% * 3/7);
+}
+
+.inner:nth-child(4){
+ transform: translate(300%, 300%);
+ width: calc(100% * 1/7);
+ height: calc(100% * 1/7);
+}
+
+.solid {
+ background-color: var(--onion-color, #000);
+}
+
+.border {
+ border: 4px solid var(--onion-color, #000);
+}
+
+.dashed {
+ border: 4px dashed var(--onion-color, #000);
+}
+
+.dotted {
+ border: 4px dotted var(--onion-color, #000);
+}
+
+.bold {
+ border: 8px solid var(--onion-color, #000);
+}
+
+.top-half {
+ width: calc(2 * var(--onion-radius, 50px));
+ height: var(--onion-radius, 50px);
+ border-radius: var(--onion-radius, 50px) var(--onion-radius, 50px) 0 0;
+ box-sizing: border-box;
+}
+
+.bottom-half {
+ width: calc(2 * var(--onion-radius, 50px));
+ height: var(--onion-radius, 50px);
+ border-radius: 0 0 var(--onion-radius, 50px) var(--onion-radius, 50px);
+ box-sizing: border-box;
+}
+
+.scaler {
+ position: absolute;
+ left:0;
+ bottom:0;
+}
\ No newline at end of file
diff --git a/browser/themes/shared/onionPattern.inc.xhtml b/browser/themes/shared/onionPattern.inc.xhtml
new file mode 100644
index 000000000000..6bbde93684a2
--- /dev/null
+++ b/browser/themes/shared/onionPattern.inc.xhtml
@@ -0,0 +1,210 @@
+<!--
+ - The abstract onion pattern begins here. There are two
+ - "onion-pattern-row" elements, each containing 14 circles. The width
+ - of "onion-pattern-row" is fixed at a value that is wide enough so the
+ - circles are not distorted by the flex-based layout. The parent
+ - "onion-pattern-container" element has overflow-x: hidden and is designed
+ - to expand to the width of the page, until it reaches a maximum width
+ - that can accommodate all 14 circles. Since the rows are wider than
+ - most browser windows, typically the two rows of onions will fill the
+ - bottom of the page. On really wide pages, the onions are centered at
+ - the bottom of the page.
+-->
+
+<div class="onion-pattern-container">
+ <!-- for some reason, these two elements are focusable, seems related to
+ - flex css somehow; disable their tabindex to fix
+ -->
+ <div class="onion-pattern-crop" tabindex="-1">
+ <div class="onion-pattern-column" tabindex="-1">
+ <div class="onion-pattern-row">
+ <div class="circle solid"></div>
+
+ <div class="circle dashed"></div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle">
+ <div class="bottom-half solid"></div>
+ <div class="bottom-half dotted"></div>
+ </div>
+
+ <div class="circle border"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle">
+ <div class="bottom-half solid"></div>
+ <div class="bottom-half solid"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle dashed"></div>
+ </div>
+
+ <div class="onion-pattern-row onion-pattern-offset-row">
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="top-half solid"></div>
+ <div class="top-half solid"></div>
+ </div>
+
+ <div class="circle border"></div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle">
+ <div class="top-half border"></div>
+ <div class="top-half dashed"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle">
+ <div class="top-half dotted"></div>
+ <div class="top-half solid"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ <div class="inner dashed"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle bold"></div>
+
+ <div class="circle solid"></div>
+
+ <div class="circle">
+ <div class="top-half solid"></div>
+ <div class="top-half dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ <div class="inner dotted"></div>
+ </div>
+
+ <div class="circle">
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ <div class="inner border"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+
+ <div class="circle">
+ <div class="top-half solid"></div>
+ <div class="top-half solid"></div>
+ </div>
+
+ <div class="circle dotted"></div>
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/browser/themes/shared/urlbar-searchbar.inc.css b/browser/themes/shared/urlbar-searchbar.inc.css
index 0158597991ec..d7dc7df17f19 100644
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -747,3 +747,5 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
}
%include ../../components/onionservices/content/onionlocation-urlbar.css
+%include ../../components/torconnect/content/torconnect-urlbar.css
+
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index b00399e2eccb..7e103e2705d1 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -17143,9 +17143,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 4da5365f214d..e981573e9822 100644
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -6213,6 +6213,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 ecdbf2a01d99..62afa98e1ffc 100644
--- a/toolkit/components/processsingleton/MainProcessSingleton.jsm
+++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm
@@ -24,6 +24,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/AsyncPrefs.jsm b/toolkit/modules/AsyncPrefs.jsm
index b1a7c8c57ec7..dd713af8b507 100644
--- a/toolkit/modules/AsyncPrefs.jsm
+++ b/toolkit/modules/AsyncPrefs.jsm
@@ -20,6 +20,7 @@ const kAllowedPrefs = new Set([
"browser.contentblocking.report.hide_vpn_banner",
"browser.contentblocking.report.show_mobile_app",
+ "extensions.torlauncher.quickstart",
"narrate.rate",
"narrate.voice",
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index c12e71ac4d42..5125203866b8 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -103,6 +103,7 @@ let RemotePageAccessManager = {
RPMGetInnerMostURI: ["*"],
RPMGetHttpResponseHeader: ["*"],
RPMGetTorStrings: ["*"],
+ RPMSendQuery: ["ShouldShowTorConnect"],
},
"about:plugins": {
RPMSendQuery: ["RequestPlugins"],
@@ -219,6 +220,21 @@ let RemotePageAccessManager = {
"FetchUpdateData",
],
},
+ "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 f4f925992027..f0a48d021638 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,
@@ -232,6 +243,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;
@@ -2676,6 +2688,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.
@@ -3169,6 +3184,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);
},
@@ -3188,6 +3232,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
@@ -6011,6 +6060,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;
@@ -6129,7 +6179,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
@@ -6251,7 +6312,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);
});
@@ -6437,6 +6498,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/"
[View Less]
1
0