tbb-commits
Threads by month
- ----- 2025 -----
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
September 2020
- 4 participants
- 214 discussions

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 32092: Fix Tor Browser Support link in preferences
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 851183a6b466c61daa45c39663c84e8ad6930991
Author: Alex Catarineu <acat(a)torproject.org>
Date: Tue Oct 15 22:54:10 2019 +0200
Bug 32092: Fix Tor Browser Support link in preferences
---
browser/components/preferences/preferences.js | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/browser/components/preferences/preferences.js b/browser/components/preferences/preferences.js
index fcb722eea0b2..9e97ca2a2180 100644
--- a/browser/components/preferences/preferences.js
+++ b/browser/components/preferences/preferences.js
@@ -166,10 +166,7 @@ function init_all() {
gotoPref().then(() => {
let helpButton = document.getElementById("helpButton");
- let helpUrl =
- Services.urlFormatter.formatURLPref("app.support.baseURL") +
- "preferences";
- helpButton.setAttribute("href", helpUrl);
+ helpButton.setAttribute("href", "https://support.torproject.org/tbb");
document.getElementById("addonsButton").addEventListener("click", e => {
if (e.button >= 2) {
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 27604: Fix addon issues when moving TB directory
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit c11dc4719afbde611b4976a622906f1a94db4d67
Author: Alex Catarineu <acat(a)torproject.org>
Date: Wed Oct 30 10:44:48 2019 +0100
Bug 27604: Fix addon issues when moving TB directory
---
toolkit/mozapps/extensions/internal/XPIProvider.jsm | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index a27888278f77..78c0b0f7c702 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -485,7 +485,7 @@ class XPIState {
// Builds prior to be 1512436 did not include the rootURI property.
// If we're updating from such a build, add that property now.
- if (!("rootURI" in this) && this.file) {
+ if (this.file) {
this.rootURI = getURIForResourceInFile(this.file, "").spec;
}
@@ -498,7 +498,10 @@ class XPIState {
saved.currentModifiedTime != this.lastModifiedTime
) {
this.lastModifiedTime = saved.currentModifiedTime;
- } else if (saved.currentModifiedTime === null) {
+ } else if (
+ saved.currentModifiedTime === null &&
+ (!this.file || !this.file.exists())
+ ) {
this.missing = true;
}
}
@@ -1439,6 +1442,7 @@ var XPIStates = {
if (oldState[loc.name]) {
loc.restore(oldState[loc.name]);
+ changed = changed || loc.path != oldState[loc.name].path;
}
changed = changed || loc.changed;
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 30237: Add v3 onion services client authentication prompt
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 6391eb0cac7cfd69b203c0e945b6ca5aee572355
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Tue Nov 12 16:11:05 2019 -0500
Bug 30237: Add v3 onion services client authentication prompt
When Tor informs the browser that client authentication is needed,
temporarily load about:blank instead of about:neterror and prompt
for the user's key.
If a correctly formatted key is entered, use Tor's ONION_CLIENT_AUTH_ADD
control port command to add the key (via Torbutton's control port
module) and reload the page.
If the user cancels the prompt, display the standard about:neterror
"Unable to connect" page. This requires a small change to
browser/actors/NetErrorChild.jsm to account for the fact that the
docShell no longer has the failedChannel information. The failedChannel
is used to extract TLS-related error info, which is not applicable
in the case of a canceled .onion authentication prompt.
Add a leaveOpen option to PopupNotifications.show so we can display
error messages within the popup notification doorhanger without
closing the prompt.
Add support for onion services strings to the TorStrings module.
Add support for Tor extended SOCKS errors (Tor proposal 304) to the
socket transport and SOCKS layers. Improved display of all of these
errors will be implemented as part of bug 30025.
Also fixes bug 19757:
Add a "Remember this key" checkbox to the client auth prompt.
Add an "Onion Services Authentication" section within the
about:preferences "Privacy & Security section" to allow
viewing and removal of v3 onion client auth keys that have
been stored on disk.
Also fixes bug 19251: use enhanced error pages for onion service errors.
---
browser/actors/NetErrorChild.jsm | 7 +
browser/base/content/aboutNetError.js | 10 +-
browser/base/content/aboutNetError.xhtml | 1 +
browser/base/content/browser.js | 10 +
browser/base/content/browser.xhtml | 3 +
browser/base/content/tab-content.js | 5 +
browser/components/moz.build | 1 +
.../content/authNotificationIcon.inc.xhtml | 6 +
.../onionservices/content/authPopup.inc.xhtml | 16 ++
.../onionservices/content/authPreferences.css | 20 ++
.../content/authPreferences.inc.xhtml | 19 ++
.../onionservices/content/authPreferences.js | 66 +++++
.../components/onionservices/content/authPrompt.js | 316 +++++++++++++++++++++
.../components/onionservices/content/authUtil.jsm | 47 +++
.../onionservices/content/netError/browser.svg | 3 +
.../onionservices/content/netError/network.svg | 3 +
.../content/netError/onionNetError.css | 65 +++++
.../content/netError/onionNetError.js | 244 ++++++++++++++++
.../onionservices/content/netError/onionsite.svg | 7 +
.../onionservices/content/onionservices.css | 69 +++++
.../onionservices/content/savedKeysDialog.js | 259 +++++++++++++++++
.../onionservices/content/savedKeysDialog.xhtml | 42 +++
browser/components/onionservices/jar.mn | 9 +
browser/components/onionservices/moz.build | 1 +
browser/components/preferences/preferences.xhtml | 1 +
browser/components/preferences/privacy.inc.xhtml | 2 +
browser/components/preferences/privacy.js | 7 +
browser/themes/shared/notification-icons.inc.css | 3 +
docshell/base/nsDocShell.cpp | 81 +++++-
dom/ipc/BrowserParent.cpp | 21 ++
dom/ipc/BrowserParent.h | 3 +
dom/ipc/PBrowser.ipdl | 9 +
js/xpconnect/src/xpc.msg | 10 +
netwerk/base/nsSocketTransport2.cpp | 6 +
netwerk/socket/nsSOCKSIOLayer.cpp | 49 ++++
toolkit/modules/PopupNotifications.jsm | 6 +
toolkit/modules/RemotePageAccessManager.jsm | 1 +
.../lib/environments/frame-script.js | 1 +
xpcom/base/ErrorList.py | 22 ++
39 files changed, 1449 insertions(+), 2 deletions(-)
diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm
index eea7ddd0cf97..58bab60c36f7 100644
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -13,6 +13,8 @@ const { RemotePageChild } = ChromeUtils.import(
"resource://gre/actors/RemotePageChild.jsm"
);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
XPCOMUtils.defineLazyServiceGetter(
this,
"gSerializationHelper",
@@ -30,6 +32,7 @@ class NetErrorChild extends RemotePageChild {
"RPMAddToHistogram",
"RPMRecordTelemetryEvent",
"RPMGetHttpResponseHeader",
+ "RPMGetTorStrings",
];
this.exportFunctions(exportableFunctions);
}
@@ -103,4 +106,8 @@ class NetErrorChild extends RemotePageChild {
return "";
}
+
+ RPMGetTorStrings() {
+ return Cu.cloneInto(TorStrings.onionServices, this.contentWindow);
+ }
}
diff --git a/browser/base/content/aboutNetError.js b/browser/base/content/aboutNetError.js
index fd12295b05fd..4e40e53da199 100644
--- a/browser/base/content/aboutNetError.js
+++ b/browser/base/content/aboutNetError.js
@@ -3,6 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env mozilla/frame-script */
+/* import-globals-from ../../components/onionservices/content/netError/onionNetError.js */
const formatter = new Intl.DateTimeFormat("default");
@@ -280,7 +281,10 @@ function initPage() {
errDesc = document.getElementById("ed_generic");
}
- setErrorPageStrings(err);
+ const isOnionError = err.startsWith("onionServices.");
+ if (!isOnionError) {
+ setErrorPageStrings(err);
+ }
var sd = document.getElementById("errorShortDescText");
if (sd) {
@@ -433,6 +437,10 @@ function initPage() {
span.textContent = document.location.hostname;
}
}
+
+ if (isOnionError) {
+ OnionServicesAboutNetError.initPage(document);
+ }
}
function setupErrorUI() {
diff --git a/browser/base/content/aboutNetError.xhtml b/browser/base/content/aboutNetError.xhtml
index 75c95cc17011..4f21e508be0b 100644
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -215,6 +215,7 @@
</div>
</div>
</body>
+ <script src="chrome://browser/content/onionservices/netError/onionNetError.js"/>
<script src="chrome://browser/content/aboutNetErrorCodes.js"/>
<script src="chrome://browser/content/aboutNetError.js"/>
</html>
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 5235b0670fa8..2fb471e9c15d 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -226,6 +226,11 @@ XPCOMUtils.defineLazyScriptGetter(
["SecurityLevelButton"],
"chrome://browser/content/securitylevel/securityLevel.js"
);
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["OnionAuthPrompt"],
+ "chrome://browser/content/onionservices/authPrompt.js"
+);
XPCOMUtils.defineLazyScriptGetter(
this,
"gEditItemOverlay",
@@ -1865,6 +1870,9 @@ var gBrowserInit = {
// Init the SecuritySettingsButton
SecurityLevelButton.init();
+ // Init the OnionAuthPrompt
+ OnionAuthPrompt.init();
+
// Certain kinds of automigration rely on this notification to complete
// their tasks BEFORE the browser window is shown. SessionStore uses it to
// restore tabs into windows AFTER important parts like gMultiProcessBrowser
@@ -2470,6 +2478,8 @@ var gBrowserInit = {
SecurityLevelButton.uninit();
+ OnionAuthPrompt.uninit();
+
gAccessibilityServiceIndicator.uninit();
AccessibilityRefreshBlocker.uninit();
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index b065ffc5f13f..7ebf56ca5ed1 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -33,6 +33,7 @@
<?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css" type="text/css"?>
<?xml-stylesheet href="chrome://torbutton/skin/tor-circuit-display.css" type="text/css"?>
<?xml-stylesheet href="chrome://torbutton/skin/torbutton.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/onionservices/onionservices.css" type="text/css"?>
# All DTD information is stored in a separate file so that it can be shared by
# hiddenWindowMac.xhtml.
@@ -654,6 +655,7 @@
#include ../../components/downloads/content/downloadsPanel.inc.xhtml
#include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml
#include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml
+#include ../../components/onionservices/content/authPopup.inc.xhtml
#include browser-allTabsMenu.inc.xhtml
<hbox id="downloads-animation-container">
@@ -1839,6 +1841,7 @@
data-l10n-id="urlbar-indexed-db-notification-anchor"/>
<image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
data-l10n-id="urlbar-password-notification-anchor"/>
+#include ../../components/onionservices/content/authNotificationIcon.inc.xhtml
<stack id="plugins-notification-icon" class="notification-anchor-icon" role="button" align="center" data-l10n-id="urlbar-plugins-notification-anchor">
<image class="plugin-icon" />
<image id="plugin-icon-badge" />
diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js
index 1a01d4a2ded9..79fd7f39243d 100644
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -19,6 +19,9 @@ ChromeUtils.defineModuleGetter(
"BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm"
);
+var { OnionAuthUtil } = ChromeUtils.import(
+ "chrome://browser/content/onionservices/authUtil.jsm"
+);
// BrowserChildGlobal
var global = this;
@@ -73,5 +76,7 @@ if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
Services.obs.notifyObservers(this, "tab-content-frameloader-created");
+OnionAuthUtil.addCancelMessageListener(this, docShell);
+
// This is a temporary hack to prevent regressions (bug 1471327).
void content;
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 09e209dc9c3b..b660be047b14 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -41,6 +41,7 @@ DIRS += [
'fxmonitor',
'migration',
'newtab',
+ 'onionservices',
'originattributes',
'pioneer',
'places',
diff --git a/browser/components/onionservices/content/authNotificationIcon.inc.xhtml b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml
new file mode 100644
index 000000000000..91274d612739
--- /dev/null
+++ b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml
@@ -0,0 +1,6 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<image id="tor-clientauth-notification-icon"
+ class="notification-anchor-icon tor-clientauth-icon"
+ role="button"
+ tooltiptext="&torbutton.onionServices.authPrompt.tooltip;"/>
diff --git a/browser/components/onionservices/content/authPopup.inc.xhtml b/browser/components/onionservices/content/authPopup.inc.xhtml
new file mode 100644
index 000000000000..bd0ec3aa0b00
--- /dev/null
+++ b/browser/components/onionservices/content/authPopup.inc.xhtml
@@ -0,0 +1,16 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<popupnotification id="tor-clientauth-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <description id="tor-clientauth-notification-desc"/>
+ <label id="tor-clientauth-notification-learnmore"
+ class="text-link popup-notification-learnmore-link"
+ is="text-link"/>
+ <html:div>
+ <html:input id="tor-clientauth-notification-key" type="password"/>
+ <html:div id="tor-clientauth-warning"/>
+ <checkbox id="tor-clientauth-persistkey-checkbox"
+ label="&torbutton.onionServices.authPrompt.persistCheckboxLabel;"/>
+ </html:div>
+ </popupnotificationcontent>
+</popupnotification>
diff --git a/browser/components/onionservices/content/authPreferences.css b/browser/components/onionservices/content/authPreferences.css
new file mode 100644
index 000000000000..b3fb79b26ddc
--- /dev/null
+++ b/browser/components/onionservices/content/authPreferences.css
@@ -0,0 +1,20 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+#torOnionServiceKeys-overview-container {
+ margin-right: 30px;
+}
+
+#onionservices-savedkeys-tree treechildren::-moz-tree-cell-text {
+ font-size: 80%;
+}
+
+#onionservices-savedkeys-errorContainer {
+ margin-top: 4px;
+ min-height: 3em;
+}
+
+#onionservices-savedkeys-errorIcon {
+ margin-right: 4px;
+ list-style-image: url("chrome://browser/skin/warning.svg");
+ visibility: hidden;
+}
diff --git a/browser/components/onionservices/content/authPreferences.inc.xhtml b/browser/components/onionservices/content/authPreferences.inc.xhtml
new file mode 100644
index 000000000000..f69c9dde66a2
--- /dev/null
+++ b/browser/components/onionservices/content/authPreferences.inc.xhtml
@@ -0,0 +1,19 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<groupbox id="torOnionServiceKeys" orient="vertical"
+ data-category="panePrivacy" hidden="true">
+ <label><html:h2 id="torOnionServiceKeys-header"/></label>
+ <hbox>
+ <description id="torOnionServiceKeys-overview-container" flex="1">
+ <html:span id="torOnionServiceKeys-overview"
+ class="tail-with-learn-more"/>
+ <label id="torOnionServiceKeys-learnMore" class="learnMore text-link"
+ is="text-link"/>
+ </description>
+ <vbox align="end">
+ <button id="torOnionServiceKeys-savedKeys"
+ is="highlightable-button"
+ class="accessory-button"/>
+ </vbox>
+ </hbox>
+</groupbox>
diff --git a/browser/components/onionservices/content/authPreferences.js b/browser/components/onionservices/content/authPreferences.js
new file mode 100644
index 000000000000..52f8272020cc
--- /dev/null
+++ b/browser/components/onionservices/content/authPreferences.js
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+/*
+ Onion Services Client Authentication Preferences Code
+
+ Code to handle init and update of onion services authentication section
+ in about:preferences#privacy
+*/
+
+const OnionServicesAuthPreferences = {
+ selector: {
+ groupBox: "#torOnionServiceKeys",
+ header: "#torOnionServiceKeys-header",
+ overview: "#torOnionServiceKeys-overview",
+ learnMore: "#torOnionServiceKeys-learnMore",
+ savedKeysButton: "#torOnionServiceKeys-savedKeys",
+ },
+
+ init() {
+ // populate XUL with localized strings
+ this._populateXUL();
+ },
+
+ _populateXUL() {
+ const groupbox = document.querySelector(this.selector.groupBox);
+
+ let elem = groupbox.querySelector(this.selector.header);
+ elem.textContent = TorStrings.onionServices.authPreferences.header;
+
+ elem = groupbox.querySelector(this.selector.overview);
+ elem.textContent = TorStrings.onionServices.authPreferences.overview;
+
+ elem = groupbox.querySelector(this.selector.learnMore);
+ elem.setAttribute("value", TorStrings.onionServices.learnMore);
+ elem.setAttribute("href", TorStrings.onionServices.learnMoreURL);
+
+ elem = groupbox.querySelector(this.selector.savedKeysButton);
+ elem.setAttribute(
+ "label",
+ TorStrings.onionServices.authPreferences.savedKeys
+ );
+ elem.addEventListener("command", () =>
+ OnionServicesAuthPreferences.onViewSavedKeys()
+ );
+ },
+
+ onViewSavedKeys() {
+ gSubDialog.open(
+ "chrome://browser/content/onionservices/savedKeysDialog.xhtml"
+ );
+ },
+}; // OnionServicesAuthPreferences
+
+Object.defineProperty(this, "OnionServicesAuthPreferences", {
+ value: OnionServicesAuthPreferences,
+ enumerable: true,
+ writable: false,
+});
diff --git a/browser/components/onionservices/content/authPrompt.js b/browser/components/onionservices/content/authPrompt.js
new file mode 100644
index 000000000000..d4a59ac46487
--- /dev/null
+++ b/browser/components/onionservices/content/authPrompt.js
@@ -0,0 +1,316 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ OnionAuthUtil: "chrome://browser/content/onionservices/authUtil.jsm",
+ CommonUtils: "resource://services-common/utils.js",
+ TorStrings: "resource:///modules/TorStrings.jsm",
+});
+
+const OnionAuthPrompt = (function() {
+ // OnionServicesAuthPrompt objects run within the main/chrome process.
+ // aReason is the topic passed within the observer notification that is
+ // causing this auth prompt to be displayed.
+ function OnionServicesAuthPrompt(aBrowser, aFailedURI, aReason, aOnionName) {
+ this._browser = aBrowser;
+ this._failedURI = aFailedURI;
+ this._reasonForPrompt = aReason;
+ this._onionName = aOnionName;
+ }
+
+ OnionServicesAuthPrompt.prototype = {
+ show(aWarningMessage) {
+ let mainAction = {
+ label: TorStrings.onionServices.authPrompt.done,
+ accessKey: TorStrings.onionServices.authPrompt.doneAccessKey,
+ leaveOpen: true, // Callback is responsible for closing the notification.
+ callback: this._onDone.bind(this),
+ };
+
+ let dialogBundle = Services.strings.createBundle(
+ "chrome://global/locale/dialog.properties");
+
+ let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel");
+ if (!cancelAccessKey)
+ cancelAccessKey = "c"; // required by PopupNotifications.show()
+
+ let cancelAction = {
+ label: dialogBundle.GetStringFromName("button-cancel"),
+ accessKey: cancelAccessKey,
+ callback: this._onCancel.bind(this),
+ };
+
+ let _this = this;
+ let options = {
+ autofocus: true,
+ hideClose: true,
+ persistent: true,
+ removeOnDismissal: false,
+ eventCallback(aTopic) {
+ if (aTopic === "showing") {
+ _this._onPromptShowing(aWarningMessage);
+ } else if (aTopic === "shown") {
+ _this._onPromptShown();
+ } else if (aTopic === "removed") {
+ _this._onPromptRemoved();
+ }
+ }
+ };
+
+ this._prompt = PopupNotifications.show(this._browser,
+ OnionAuthUtil.domid.notification, "",
+ OnionAuthUtil.domid.anchor,
+ mainAction, [cancelAction], options);
+ },
+
+ _onPromptShowing(aWarningMessage) {
+ let xulDoc = this._browser.ownerDocument;
+ let descElem = xulDoc.getElementById(OnionAuthUtil.domid.description);
+ if (descElem) {
+ // Handle replacement of the onion name within the localized
+ // string ourselves so we can show the onion name as bold text.
+ // We do this by splitting the localized string and creating
+ // several HTML <span> elements.
+ while (descElem.firstChild)
+ descElem.removeChild(descElem.firstChild);
+
+ let fmtString = TorStrings.onionServices.authPrompt.description;
+ let prefix = "";
+ let suffix = "";
+ const kToReplace = "%S";
+ let idx = fmtString.indexOf(kToReplace);
+ if (idx < 0) {
+ prefix = fmtString;
+ } else {
+ prefix = fmtString.substring(0, idx);
+ suffix = fmtString.substring(idx + kToReplace.length);
+ }
+
+ const kHTMLNS = "http://www.w3.org/1999/xhtml";
+ let span = xulDoc.createElementNS(kHTMLNS, "span");
+ span.textContent = prefix;
+ descElem.appendChild(span);
+ span = xulDoc.createElementNS(kHTMLNS, "span");
+ span.id = OnionAuthUtil.domid.onionNameSpan;
+ span.textContent = this._onionName;
+ descElem.appendChild(span);
+ span = xulDoc.createElementNS(kHTMLNS, "span");
+ span.textContent = suffix;
+ descElem.appendChild(span);
+ }
+
+ // Set "Learn More" label and href.
+ let learnMoreElem = xulDoc.getElementById(OnionAuthUtil.domid.learnMore);
+ if (learnMoreElem) {
+ learnMoreElem.setAttribute("value", TorStrings.onionServices.learnMore);
+ learnMoreElem.setAttribute("href", TorStrings.onionServices.learnMoreURL);
+ }
+
+ this._showWarning(aWarningMessage);
+ let checkboxElem = this._getCheckboxElement();
+ if (checkboxElem) {
+ checkboxElem.checked = false;
+ }
+ },
+
+ _onPromptShown() {
+ let keyElem = this._getKeyElement();
+ if (keyElem) {
+ keyElem.setAttribute("placeholder",
+ TorStrings.onionServices.authPrompt.keyPlaceholder);
+ this._boundOnKeyFieldKeyPress = this._onKeyFieldKeyPress.bind(this);
+ this._boundOnKeyFieldInput = this._onKeyFieldInput.bind(this);
+ keyElem.addEventListener("keypress", this._boundOnKeyFieldKeyPress);
+ keyElem.addEventListener("input", this._boundOnKeyFieldInput);
+ keyElem.focus();
+ }
+ },
+
+ _onPromptRemoved() {
+ if (this._boundOnKeyFieldKeyPress) {
+ let keyElem = this._getKeyElement();
+ if (keyElem) {
+ keyElem.value = "";
+ keyElem.removeEventListener("keypress",
+ this._boundOnKeyFieldKeyPress);
+ this._boundOnKeyFieldKeyPress = undefined;
+ keyElem.removeEventListener("input", this._boundOnKeyFieldInput);
+ this._boundOnKeyFieldInput = undefined;
+ }
+ }
+ },
+
+ _onKeyFieldKeyPress(aEvent) {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ this._onDone();
+ } else if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ this._prompt.remove();
+ this._onCancel();
+ }
+ },
+
+ _onKeyFieldInput(aEvent) {
+ this._showWarning(undefined); // Remove the warning.
+ },
+
+ _onDone() {
+ let keyElem = this._getKeyElement();
+ if (!keyElem)
+ return;
+
+ let base64key = this._keyToBase64(keyElem.value);
+ if (!base64key) {
+ this._showWarning(TorStrings.onionServices.authPrompt.invalidKey);
+ return;
+ }
+
+ this._prompt.remove();
+
+ // Use Torbutton's controller module to add the private key to Tor.
+ let controllerFailureMsg =
+ TorStrings.onionServices.authPrompt.failedToSetKey;
+ try {
+ let { controller } =
+ Cu.import("resource://torbutton/modules/tor-control-port.js", {});
+ let torController = controller(aError => {
+ this.show(controllerFailureMsg);
+ });
+ let onionAddr = this._onionName.toLowerCase().replace(/\.onion$/, "");
+ let checkboxElem = this._getCheckboxElement();
+ let isPermanent = (checkboxElem && checkboxElem.checked);
+ torController.onionAuthAdd(onionAddr, base64key, isPermanent)
+ .then(aResponse => {
+ // Success! Reload the page.
+ this._browser.sendMessageToActor(
+ "Browser:Reload",
+ {},
+ "BrowserTab"
+ );
+ })
+ .catch(aError => {
+ if (aError.torMessage)
+ this.show(aError.torMessage);
+ else
+ this.show(controllerFailureMsg);
+ });
+ } catch (e) {
+ this.show(controllerFailureMsg);
+ }
+ },
+
+ _onCancel() {
+ // Arrange for an error page to be displayed.
+ this._browser.messageManager.sendAsyncMessage(
+ OnionAuthUtil.message.authPromptCanceled,
+ {failedURI: this._failedURI.spec,
+ reasonForPrompt: this._reasonForPrompt});
+ },
+
+ _getKeyElement() {
+ let xulDoc = this._browser.ownerDocument;
+ return xulDoc.getElementById(OnionAuthUtil.domid.keyElement);
+ },
+
+ _getCheckboxElement() {
+ let xulDoc = this._browser.ownerDocument;
+ return xulDoc.getElementById(OnionAuthUtil.domid.checkboxElement);
+ },
+
+ _showWarning(aWarningMessage) {
+ let xulDoc = this._browser.ownerDocument;
+ let warningElem =
+ xulDoc.getElementById(OnionAuthUtil.domid.warningElement);
+ let keyElem = this._getKeyElement();
+ if (warningElem) {
+ if (aWarningMessage) {
+ warningElem.textContent = aWarningMessage;
+ warningElem.removeAttribute("hidden");
+ if (keyElem)
+ keyElem.className = "invalid";
+ } else {
+ warningElem.setAttribute("hidden", "true");
+ if (keyElem)
+ keyElem.className = "";
+ }
+ }
+ },
+
+ // Returns undefined if the key is the wrong length or format.
+ _keyToBase64(aKeyString) {
+ if (!aKeyString)
+ return undefined;
+
+ let base64key;
+ if (aKeyString.length == 52) {
+ // The key is probably base32-encoded. Attempt to decode.
+ // Although base32 specifies uppercase letters, we accept lowercase
+ // as well because users may type in lowercase or copy a key out of
+ // a tor onion-auth file (which uses lowercase).
+ let rawKey;
+ try {
+ rawKey = CommonUtils.decodeBase32(aKeyString.toUpperCase());
+ } catch (e) {}
+
+ if (rawKey) try {
+ base64key = btoa(rawKey);
+ } catch (e) {}
+ } else if ((aKeyString.length == 44) &&
+ /^[a-zA-Z0-9+/]*=*$/.test(aKeyString)) {
+ // The key appears to be a correctly formatted base64 value. If not,
+ // tor will return an error when we try to add the key via the
+ // control port.
+ base64key = aKeyString;
+ }
+
+ return base64key;
+ },
+ };
+
+ let retval = {
+ init() {
+ Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthMissing);
+ Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthIncorrect);
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthMissing);
+ Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthIncorrect);
+ },
+
+ // aSubject is the DOM Window or browser where the prompt should be shown.
+ // aData contains the .onion name.
+ observe(aSubject, aTopic, aData) {
+ if ((aTopic != OnionAuthUtil.topic.clientAuthMissing) &&
+ (aTopic != OnionAuthUtil.topic.clientAuthIncorrect)) {
+ return;
+ }
+
+ let browser;
+ if (aSubject instanceof Ci.nsIDOMWindow) {
+ let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ browser = contentWindow.docShell.chromeEventHandler;
+ } else {
+ browser = aSubject.QueryInterface(Ci.nsIBrowser);
+ }
+
+ if (!gBrowser.browsers.some(aBrowser => aBrowser == browser)) {
+ return; // This window does not contain the subject browser; ignore.
+ }
+
+ let failedURI = browser.currentURI;
+ let authPrompt = new OnionServicesAuthPrompt(browser, failedURI,
+ aTopic, aData);
+ authPrompt.show(undefined);
+ }
+ };
+
+ return retval;
+})(); /* OnionAuthPrompt */
+
+
+Object.defineProperty(this, "OnionAuthPrompt", {
+ value: OnionAuthPrompt,
+ enumerable: true,
+ writable: false
+});
diff --git a/browser/components/onionservices/content/authUtil.jsm b/browser/components/onionservices/content/authUtil.jsm
new file mode 100644
index 000000000000..c9d83774da1f
--- /dev/null
+++ b/browser/components/onionservices/content/authUtil.jsm
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "OnionAuthUtil",
+];
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const OnionAuthUtil = {
+ topic: {
+ clientAuthMissing: "tor-onion-services-clientauth-missing",
+ clientAuthIncorrect: "tor-onion-services-clientauth-incorrect",
+ },
+ message: {
+ authPromptCanceled: "Tor:OnionServicesAuthPromptCanceled",
+ },
+ domid: {
+ anchor: "tor-clientauth-notification-icon",
+ notification: "tor-clientauth",
+ description: "tor-clientauth-notification-desc",
+ learnMore: "tor-clientauth-notification-learnmore",
+ onionNameSpan: "tor-clientauth-notification-onionname",
+ keyElement: "tor-clientauth-notification-key",
+ warningElement: "tor-clientauth-warning",
+ checkboxElement: "tor-clientauth-persistkey-checkbox",
+ },
+
+ addCancelMessageListener(aTabContent, aDocShell) {
+ aTabContent.addMessageListener(this.message.authPromptCanceled,
+ (aMessage) => {
+ // Upon cancellation of the client authentication prompt, display
+ // the appropriate error page. When calling the docShell
+ // displayLoadError() function, we pass undefined for the failed
+ // channel so that displayLoadError() can determine that it should
+ // not display the client authentication prompt a second time.
+ let failedURI = Services.io.newURI(aMessage.data.failedURI);
+ let reasonForPrompt = aMessage.data.reasonForPrompt;
+ let errorCode =
+ (reasonForPrompt === this.topic.clientAuthMissing) ?
+ Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH :
+ Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH;
+ aDocShell.displayLoadError(errorCode, failedURI, undefined, undefined);
+ });
+ },
+};
diff --git a/browser/components/onionservices/content/netError/browser.svg b/browser/components/onionservices/content/netError/browser.svg
new file mode 100644
index 000000000000..b4c433b37bbb
--- /dev/null
+++ b/browser/components/onionservices/content/netError/browser.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="72" height="65" viewBox="0 0 72 65">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M0.0 0.0C0.0 0.0 0.0 65.0 0.0 65.0C0.0 65.0 72.0 65.0 72.0 65.0C72.0 65.0 72.0 0.0 72.0 0.0C72.0 0.0 52.9019692 0.0 52.9019692 0.0C52.9019692 0.0 0.0 0.0 0.0 0.0C0.0 0.0 0.0 0.0 0.0 0.0M65.0 58.0C65.0 58.0 6.0 58.0 6.0 58.0C6.0 58.0 6.0 25.0 6.0 25.0C6.0 25.0 65.0 25.0 65.0 25.0C65.0 25.0 65.0 58.0 65.0 58.0C65.0 58.0 65.0 58.0 65.0 58.0M6.0 10.0C6.0 10.0 10.0 10.0 10.0 10.0C10.0 10.0 10.0 14.0 10.0 14.0C10.0 14.0 6.0 14.0 6.0 14.0C6.0 14.0 6.0 10.0 6.0 10.0C6.0 10.0 6.0 10.0 6.0 10.0M14.0 10.0C14.0 10.0 18.0 10.0 18.0 10.0C18.0 10.0 18.0 14.0 18.0 14.0C18.0 14.0 14.0 14.0 14.0 14.0C14.0 14.0 14.0 10.0 14.0 10.0C14.0 10.0 14.0 10.0 14.0 10.0M22.0 10.0C22.0 10.0 26.0 10.0 26.0 10.0C26.0 10.0 26.0 14.0 26.0 14.0C26.0 14.0 22.0 14.0 22.0 14.0C22.0 14.0 22.0 10.0 22.0 10.0C22.0 10.0 22.0 10.0 22.0 10.0" />
+</svg>
diff --git a/browser/components/onionservices/content/netError/network.svg b/browser/components/onionservices/content/netError/network.svg
new file mode 100644
index 000000000000..808c53dedd09
--- /dev/null
+++ b/browser/components/onionservices/content/netError/network.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="72" height="54" viewBox="0 0 72 54">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M14.0487805 54.0C6.28990244 54.0 0.0 47.3306322 0.0 39.1034585C0.0 32.0105634 4.68716488 26.0867675 10.9481707 24.585103C10.6902 23.574652 10.5365854 22.5107596 10.5365854 21.4138156C10.5365854 14.7292347 15.6471278 9.3103384 21.9512195 9.3103384C24.8076351 9.3103384 27.4126741 10.4393194 29.4146341 12.2780088C32.1344254 5.0777841 38.77452 0.0 46.5365854 0.0C56.7201249 0.0 64.9756098 8.7536733 64.9756098 19.5517479C64.9756098 20.7691677 64.8471688 21.9453428 64.6463415 23.1013144C69.0576849 26.0679606 72.0 31.2693674 72.0 37.2413909C72.0 46.5256603 64.9510244 54.0 56.195122 54.0C56.195122 54.0 14.0487805 54.0 14.0487805 54.0C14.0487805 54.0 14.0487805 54.0 14.0487805 54.0" />
+</svg>
diff --git a/browser/components/onionservices/content/netError/onionNetError.css b/browser/components/onionservices/content/netError/onionNetError.css
new file mode 100644
index 000000000000..58117ab93223
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionNetError.css
@@ -0,0 +1,65 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+:root {
+ --grey-70: #38383d;
+}
+
+#onionErrorDiagramContainer {
+ margin: 60px auto;
+ width: 460px; /* 3 columns @ 140px plus 2 column gaps @ 20px */
+ display: grid;
+ grid-row-gap: 15px;
+ grid-column-gap: 20px;
+ grid-template-columns: 1fr 1fr 1fr;
+}
+
+#onionErrorDiagramContainer > div {
+ margin: auto;
+ position: relative; /* needed to allow overlay of the ok or error icon */
+}
+
+.onionErrorImage {
+ width: 72px;
+ height: 72px;
+ background-position: center;
+ background-repeat: no-repeat;
+ -moz-context-properties: fill;
+ fill: var(--grey-70);
+}
+
+#onionErrorBrowserImage {
+ background-image: url("browser.svg");
+}
+
+#onionErrorNetworkImage {
+ background-image: url("network.svg");
+}
+
+#onionErrorOnionSiteImage {
+ background-image: url("onionsite.svg");
+}
+
+/* rules to support overlay of the ok or error icon */
+.onionErrorImage[status]::after {
+ content: " ";
+ position: absolute;
+ left: -18px;
+ top: 18px;
+ width: 36px;
+ height: 36px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-page-background);
+ background-color: var(--grey-70);
+ background-repeat: no-repeat;
+ background-position: center;
+ border: 3px solid var(--in-content-page-background);
+ border-radius: 50%;
+}
+
+.onionErrorImage[status="ok"]::after {
+ background-image: url("chrome://global/skin/icons/check.svg");
+}
+
+.onionErrorImage[status="error"]::after {
+ background-image: url("chrome://browser/skin/stop.svg");
+}
diff --git a/browser/components/onionservices/content/netError/onionNetError.js b/browser/components/onionservices/content/netError/onionNetError.js
new file mode 100644
index 000000000000..8fabb3f38eb7
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionNetError.js
@@ -0,0 +1,244 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+/* eslint-env mozilla/frame-script */
+
+var OnionServicesAboutNetError = {
+ _selector: {
+ header: ".title-text",
+ longDesc: "#errorLongDesc",
+ learnMoreContainer: "#learnMoreContainer",
+ learnMoreLink: "#learnMoreLink",
+ contentContainer: "#errorLongContent",
+ tryAgainButtonContainer: "#netErrorButtonContainer",
+ },
+ _status: {
+ ok: "ok",
+ error: "error",
+ },
+
+ _diagramInfoMap: undefined,
+
+ // Public functions (called from outside this file).
+ //
+ // This initPage() function may need to be updated if the structure of
+ // browser/base/content/aboutNetError.xhtml changes. Specifically, it
+ // references the following elements:
+ // query string parameter e
+ // class title-text
+ // id errorLongDesc
+ // id learnMoreContainer
+ // id learnMoreLink
+ // id errorLongContent
+ initPage(aDoc) {
+ const searchParams = new URLSearchParams(aDoc.documentURI.split("?")[1]);
+ const err = searchParams.get("e");
+
+ const errPrefix = "onionServices.";
+ const errName = err.substring(errPrefix.length);
+
+ this._strings = RPMGetTorStrings();
+
+ const stringsObj = this._strings[errName];
+ if (!stringsObj) {
+ return;
+ }
+
+ this._insertStylesheet(aDoc);
+
+ const pageTitle = stringsObj.pageTitle;
+ const header = stringsObj.header;
+ const longDescription = stringsObj.longDescription; // optional
+ const learnMoreURL = stringsObj.learnMoreURL;
+
+ if (pageTitle) {
+ aDoc.title = pageTitle;
+ }
+
+ if (header) {
+ const headerElem = aDoc.querySelector(this._selector.header);
+ if (headerElem) {
+ headerElem.textContent = header;
+ }
+ }
+
+ const ld = aDoc.querySelector(this._selector.longDesc);
+ if (ld) {
+ if (longDescription) {
+ const hexErr = this._hexErrorFromName(errName);
+ ld.textContent = longDescription.replace("%S", hexErr);
+ } else {
+ // This onion service error does not have a long description. Since
+ // it is set to a generic error string by the code in
+ // browser/base/content/aboutNetError.js, hide it here.
+ ld.style.display = "none";
+ }
+ }
+
+ if (learnMoreURL) {
+ const lmContainer = aDoc.querySelector(this._selector.learnMoreContainer);
+ if (lmContainer) {
+ lmContainer.style.display = "block";
+ }
+ const lmLink = lmContainer.querySelector(this._selector.learnMoreLink);
+ if (lmLink) {
+ lmLink.setAttribute("href", learnMoreURL);
+ }
+ }
+
+ // Remove the "Try Again" button if the user made a typo in the .onion
+ // address since it is not useful in that case.
+ if (errName === "badAddress") {
+ const tryAgainButton = aDoc.querySelector(
+ this._selector.tryAgainButtonContainer
+ );
+ if (tryAgainButton) {
+ tryAgainButton.style.display = "none";
+ }
+ }
+
+ this._insertDiagram(aDoc, errName);
+ }, // initPage()
+
+ _insertStylesheet(aDoc) {
+ const url =
+ "chrome://browser/content/onionservices/netError/onionNetError.css";
+ let linkElem = aDoc.createElement("link");
+ linkElem.rel = "stylesheet";
+ linkElem.href = url;
+ linkElem.type = "text/css";
+ aDoc.head.appendChild(linkElem);
+ },
+
+ _insertDiagram(aDoc, aErrorName) {
+ // The onion error diagram consists of a grid of div elements.
+ // The first row contains three images (Browser, Network, Onionsite) and
+ // the second row contains labels for the images that are in the first row.
+ // The _diagramInfoMap describes for each type of onion service error
+ // whether a small ok or error status icon is overlaid on top of the main
+ // Browser/Network/Onionsite images.
+ if (!this._diagramInfoMap) {
+ this._diagramInfoMap = new Map();
+ this._diagramInfoMap.set("descNotFound", {
+ browser: this._status.ok,
+ network: this._status.ok,
+ onionSite: this._status.error,
+ });
+ this._diagramInfoMap.set("descInvalid", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ this._diagramInfoMap.set("introFailed", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ this._diagramInfoMap.set("rendezvousFailed", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ this._diagramInfoMap.set("clientAuthMissing", {
+ browser: this._status.error,
+ });
+ this._diagramInfoMap.set("clientAuthIncorrect", {
+ browser: this._status.error,
+ });
+ this._diagramInfoMap.set("badAddress", {
+ browser: this._status.error,
+ });
+ this._diagramInfoMap.set("introTimedOut", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ }
+
+ const diagramInfo = this._diagramInfoMap.get(aErrorName);
+
+ const container = this._createDiv(aDoc, "onionErrorDiagramContainer");
+ const imageClass = "onionErrorImage";
+
+ const browserImage = this._createDiv(
+ aDoc,
+ "onionErrorBrowserImage",
+ imageClass,
+ container
+ );
+ if (diagramInfo && diagramInfo.browser) {
+ browserImage.setAttribute("status", diagramInfo.browser);
+ }
+
+ const networkImage = this._createDiv(
+ aDoc,
+ "onionErrorNetworkImage",
+ imageClass,
+ container
+ );
+ if (diagramInfo && diagramInfo.network) {
+ networkImage.setAttribute("status", diagramInfo.network);
+ }
+
+ const onionSiteImage = this._createDiv(
+ aDoc,
+ "onionErrorOnionSiteImage",
+ imageClass,
+ container
+ );
+ if (diagramInfo && diagramInfo.onionSite) {
+ onionSiteImage.setAttribute("status", diagramInfo.onionSite);
+ }
+
+ let labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+ labelDiv.textContent = this._strings.errorPage.browser;
+ labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+ labelDiv.textContent = this._strings.errorPage.network;
+ labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+ labelDiv.textContent = this._strings.errorPage.onionSite;
+
+ const contentContainer = aDoc.querySelector(
+ this._selector.contentContainer
+ );
+ if (contentContainer) {
+ contentContainer.insertBefore(container, contentContainer.firstChild);
+ }
+ }, // _insertDiagram()
+
+ _createDiv(aDoc, aID, aClass, aParentElem) {
+ const div = aDoc.createElement("div");
+ if (aID) {
+ div.id = aID;
+ }
+ if (aClass) {
+ div.setAttribute("class", aClass);
+ }
+ if (aParentElem) {
+ aParentElem.appendChild(div);
+ }
+
+ return div;
+ },
+
+ _hexErrorFromName(aErrorName) {
+ // We do not have access to the original Tor SOCKS error code here, so
+ // perform a reverse mapping from the error name.
+ switch (aErrorName) {
+ case "descNotFound":
+ return "0xF0";
+ case "descInvalid":
+ return "0xF1";
+ case "introFailed":
+ return "0xF2";
+ case "rendezvousFailed":
+ return "0xF3";
+ case "clientAuthMissing":
+ return "0xF4";
+ case "clientAuthIncorrect":
+ return "0xF5";
+ case "badAddress":
+ return "0xF6";
+ case "introTimedOut":
+ return "0xF7";
+ }
+
+ return "";
+ },
+};
diff --git a/browser/components/onionservices/content/netError/onionsite.svg b/browser/components/onionservices/content/netError/onionsite.svg
new file mode 100644
index 000000000000..1f2777e6acc7
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionsite.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="70" height="63" viewBox="0 0 70 63">
+ <g fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="M64.0 2.0C64.0 2.0 4.0 2.0 4.0 2.0C2.8954305 2.0 2.0 2.81148389 2.0 3.8125C2.0 3.8125 2.0 58.1875 2.0 58.1875C2.0 59.1885161 2.8954305 60.0 4.0 60.0C4.0 60.0 36.0 60.0 36.0 60.0C36.0 60.0 36.0 56.375 36.0 56.375C36.0 56.375 6.0 56.375 6.0 56.375C6.0 56.375 6.0 41.875 6.0 41.875C6.0 41.875 38.0 41.875 38.0 41.875C38.0 41.875 38.0 38.25 38.0 38.25C38.0 38.25 6.0 38.25 6.0 38.25C6.0 38.25 6.0 23.75 6.0 23.75C6.0 23.75 62.0 23.75 62.0 23.75C62.0 23.75 62.0 36.4375 62.0 36.4375C62.0 36.4375 66.0 36.4375 66.0 36.4375C66.0 36.4375 66.0 3.8125 66.0 3.8125C66.0 2.81148389 65.1045695 2.0 64.0 2.0C64.0 2.0 64.0 2.0 64.0 2.0M62.0 20.125C62.0 20.125 6.0 20.125 6.0 20.125C6.0 20.125 6.0 5.625 6.0 5.625C6.0 5.625 62.0 5.625 62.0 5.625C62.0 5.625 62.0 20.125 62.0 20.125C62.0 20.125 62.0 20.125 62.0 20.125" />
+ <path d="M24.0 47.0C24.0 47.0 24.0 51.0 24.0 51.0C24.0 51.0 20.0 51.0 20.0 51.0C20.0 51.0 20.0 47.0 20.0 47.0C20.0 47.0 24.0 47.0 24.0 47.0C24.0 47.0 24.0 47.0 24.0 47.0M16.0 47.0C16.0 47.0 16.0 51.0 16.0 51.0C16.0 51.0 12.0 51.0 12.0 51.0C12.0 51.0 12.0 47.0 12.0 47.0C12.0 47.0 16.0 47.0 16.0 47.0C16.0 47.0 16.0 47.0 16.0 47.0M56.0 29.0C56.0 29.0 56.0 33.0 56.0 33.0C56.0 33.0 52.0 33.0 52.0 33.0C52.0 33.0 52.0 29.0 52.0 29.0C52.0 29.0 56.0 29.0 56.0 29.0C56.0 29.0 56.0 29.0 56.0 29.0M48.0 29.0C48.0 29.0 48.0 33.0 48.0 33.0C48.0 33.0 12.0 33.0 12.0 33.0C12.0 33.0 12.0 29.0 12.0 29.0C12.0 29.0 48.0 29.0 48.0 29.0C48.0 29.0 48.0 29.0 48.0 29.0M22.0 11.0C22.0 11.0 22.0 15.0 22.0 15.0C22.0 15.0 10.0 15.0 10.0 15.0C10.0 15.0 10.0 11.0 10.0 11.0C10.0 11.0 22.0 11.0 22.0 11.0C22.0 11.0 22.0 11.0 22.0 11.0M70.0 0.0C70.0 0.0 70.0 36.5 70.0 36.5C70.0 36.5 65.0 36.5 65.0 36.5C65.0 36.5 65.0 4.5 65.0 4.5C65.0 4.5 5.0 4.5 5.0 4.5C5.0 4.5 5.0 58.5 5.0 58.5C5.0 58.5 36.0 58.5 36.0 58.5C36.0 58
.5 36.0 63.0 36.0 63.0C36.0 63.0 0.0 63.0 0.0 63.0C0.0 63.0 0.0 0.0 0.0 0.0C0.0 0.0 70.0 0.0 70.0 0.0C70.0 0.0 70.0 0.0 70.0 0.0M32.0 47.0C32.0 47.0 32.0 51.0 32.0 51.0C32.0 51.0 28.0 51.0 28.0 51.0C28.0 51.0 28.0 47.0 28.0 47.0C28.0 47.0 32.0 47.0 32.0 47.0C32.0 47.0 32.0 47.0 32.0 47.0M54.0 11.0C54.0 11.0 54.0 15.0 54.0 15.0C54.0 15.0 50.0 15.0 50.0 15.0C50.0 15.0 50.0 11.0 50.0 11.0C50.0 11.0 54.0 11.0 54.0 11.0C54.0 11.0 54.0 11.0 54.0 11.0M46.0 11.0C46.0 11.0 46.0 15.0 46.0 15.0C46.0 15.0 42.0 15.0 42.0 15.0C42.0 15.0 42.0 11.0 42.0 11.0C42.0 11.0 46.0 11.0 46.0 11.0C46.0 11.0 46.0 11.0 46.0 11.0M38.0 11.0C38.0 11.0 38.0 15.0 38.0 15.0C38.0 15.0 34.0 15.0 34.0 15.0C34.0 15.0 34.0 11.0 34.0 11.0C34.0 11.0 38.0 11.0 38.0 11.0C38.0 11.0 38.0 11.0 38.0 11.0M30.0 11.0C30.0 11.0 30.0 15.0 30.0 15.0C30.0 15.0 26.0 15.0 26.0 15.0C26.0 15.0 26.0 11.0 26.0 11.0C26.0 11.0 30.0 11.0 30.0 11.0C30.0 11.0 30.0 11.0 30.0 11.0" />
+ <path d="M61.0 46.0C61.0 46.0 59.0 46.0 59.0 46.0C59.0 46.0 59.0 40.0 59.0 40.0C59.0 38.8954305 58.1045695 38.0 57.0 38.0C57.0 38.0 49.0 38.0 49.0 38.0C47.8954305 38.0 47.0 38.8954305 47.0 40.0C47.0 40.0 47.0 46.0 47.0 46.0C47.0 46.0 45.0 46.0 45.0 46.0C43.8954305 46.0 43.0 46.8954305 43.0 48.0C43.0 48.0 43.0 60.0 43.0 60.0C43.0 61.1045695 43.8954305 62.0 45.0 62.0C45.0 62.0 61.0 62.0 61.0 62.0C62.1045695 62.0 63.0 61.1045695 63.0 60.0C63.0 60.0 63.0 48.0 63.0 48.0C63.0 46.8954305 62.1045695 46.0 61.0 46.0C61.0 46.0 61.0 46.0 61.0 46.0M51.0 42.0C51.0 42.0 55.0 42.0 55.0 42.0C55.0 42.0 55.0 46.0 55.0 46.0C55.0 46.0 51.0 46.0 51.0 46.0C51.0 46.0 51.0 42.0 51.0 42.0C51.0 42.0 51.0 42.0 51.0 42.0M59.0 58.0C59.0 58.0 47.0 58.0 47.0 58.0C47.0 58.0 47.0 50.0 47.0 50.0C47.0 50.0 59.0 50.0 59.0 50.0C59.0 50.0 59.0 58.0 59.0 58.0C59.0 58.0 59.0 58.0 59.0 58.0" />
+ </g>
+</svg>
diff --git a/browser/components/onionservices/content/onionservices.css b/browser/components/onionservices/content/onionservices.css
new file mode 100644
index 000000000000..e2621ec8266d
--- /dev/null
+++ b/browser/components/onionservices/content/onionservices.css
@@ -0,0 +1,69 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|*#tor-clientauth-notification-onionname {
+ font-weight: bold;
+}
+
+html|*#tor-clientauth-notification-key {
+ box-sizing: border-box;
+ width: 100%;
+ margin-top: 15px;
+ padding: 6px;
+}
+
+/* Start of rules adapted from
+ * browser/components/newtab/css/activity-stream-mac.css (linux and windows
+ * use the same rules).
+ */
+html|*#tor-clientauth-notification-key.invalid {
+ border: 1px solid #D70022;
+ box-shadow: 0 0 0 1px #D70022, 0 0 0 4px rgba(215, 0, 34, 0.3);
+}
+
+html|*#tor-clientauth-warning {
+ display: inline-block;
+ animation: fade-up-tt 450ms;
+ background: #D70022;
+ border-radius: 2px;
+ color: #FFF;
+ inset-inline-start: 3px;
+ padding: 5px 12px;
+ position: relative;
+ top: 6px;
+ z-index: 1;
+}
+
+html|*#tor-clientauth-warning[hidden] {
+ display: none;
+}
+
+html|*#tor-clientauth-warning::before {
+ background: #D70022;
+ bottom: -8px;
+ content: '.';
+ height: 16px;
+ inset-inline-start: 12px;
+ position: absolute;
+ text-indent: -999px;
+ top: -7px;
+ transform: rotate(45deg);
+ white-space: nowrap;
+ width: 16px;
+ z-index: -1;
+}
+
+@keyframes fade-up-tt {
+ 0% {
+ opacity: 0;
+ transform: translateY(15px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+/* End of rules adapted from
+ * browser/components/newtab/css/activity-stream-mac.css
+ */
diff --git a/browser/components/onionservices/content/savedKeysDialog.js b/browser/components/onionservices/content/savedKeysDialog.js
new file mode 100644
index 000000000000..b1376bbabe85
--- /dev/null
+++ b/browser/components/onionservices/content/savedKeysDialog.js
@@ -0,0 +1,259 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "controller",
+ "resource://torbutton/modules/tor-control-port.js"
+);
+
+var gOnionServicesSavedKeysDialog = {
+ selector: {
+ dialog: "#onionservices-savedkeys-dialog",
+ intro: "#onionservices-savedkeys-intro",
+ tree: "#onionservices-savedkeys-tree",
+ onionSiteCol: "#onionservices-savedkeys-siteCol",
+ onionKeyCol: "#onionservices-savedkeys-keyCol",
+ errorIcon: "#onionservices-savedkeys-errorIcon",
+ errorMessage: "#onionservices-savedkeys-errorMessage",
+ removeButton: "#onionservices-savedkeys-remove",
+ removeAllButton: "#onionservices-savedkeys-removeall",
+ },
+
+ _tree: undefined,
+ _isBusy: false, // true when loading data, deleting a key, etc.
+
+ // Public functions (called from outside this file).
+ async deleteSelectedKeys() {
+ this._setBusyState(true);
+
+ const indexesToDelete = [];
+ const count = this._tree.view.selection.getRangeCount();
+ for (let i = 0; i < count; ++i) {
+ const minObj = {};
+ const maxObj = {};
+ this._tree.view.selection.getRangeAt(i, minObj, maxObj);
+ for (let idx = minObj.value; idx <= maxObj.value; ++idx) {
+ indexesToDelete.push(idx);
+ }
+ }
+
+ if (indexesToDelete.length > 0) {
+ const controllerFailureMsg =
+ TorStrings.onionServices.authPreferences.failedToRemoveKey;
+ try {
+ const torController = controller(aError => {
+ this._showError(controllerFailureMsg);
+ });
+
+ // Remove in reverse index order to avoid issues caused by index changes.
+ for (let i = indexesToDelete.length - 1; i >= 0; --i) {
+ await this._deleteOneKey(torController, indexesToDelete[i]);
+ }
+ } catch (e) {
+ if (e.torMessage) {
+ this._showError(e.torMessage);
+ } else {
+ this._showError(controllerFailureMsg);
+ }
+ }
+ }
+
+ this._setBusyState(false);
+ },
+
+ async deleteAllKeys() {
+ this._tree.view.selection.selectAll();
+ await this.deleteSelectedKeys();
+ },
+
+ updateButtonsState() {
+ const haveSelection = this._tree.view.selection.getRangeCount() > 0;
+ const dialog = document.querySelector(this.selector.dialog);
+ const removeSelectedBtn = dialog.querySelector(this.selector.removeButton);
+ removeSelectedBtn.disabled = this._isBusy || !haveSelection;
+ const removeAllBtn = dialog.querySelector(this.selector.removeAllButton);
+ removeAllBtn.disabled = this._isBusy || this.rowCount === 0;
+ },
+
+ // Private functions.
+ _onLoad() {
+ document.mozSubdialogReady = this._init();
+ },
+
+ async _init() {
+ await this._populateXUL();
+
+ window.addEventListener("keypress", this._onWindowKeyPress.bind(this));
+
+ // We don't use await here because we want _loadSavedKeys() to run
+ // in the background and not block loading of this dialog.
+ this._loadSavedKeys();
+ },
+
+ async _populateXUL() {
+ const dialog = document.querySelector(this.selector.dialog);
+ const authPrefStrings = TorStrings.onionServices.authPreferences;
+ dialog.setAttribute("title", authPrefStrings.dialogTitle);
+
+ let elem = dialog.querySelector(this.selector.intro);
+ elem.textContent = authPrefStrings.dialogIntro;
+
+ elem = dialog.querySelector(this.selector.onionSiteCol);
+ elem.setAttribute("label", authPrefStrings.onionSite);
+
+ elem = dialog.querySelector(this.selector.onionKeyCol);
+ elem.setAttribute("label", authPrefStrings.onionKey);
+
+ elem = dialog.querySelector(this.selector.removeButton);
+ elem.setAttribute("label", authPrefStrings.remove);
+
+ elem = dialog.querySelector(this.selector.removeAllButton);
+ elem.setAttribute("label", authPrefStrings.removeAll);
+
+ this._tree = dialog.querySelector(this.selector.tree);
+ },
+
+ async _loadSavedKeys() {
+ const controllerFailureMsg =
+ TorStrings.onionServices.authPreferences.failedToGetKeys;
+ this._setBusyState(true);
+
+ try {
+ this._tree.view = this;
+
+ const torController = controller(aError => {
+ this._showError(controllerFailureMsg);
+ });
+
+ const keyInfoList = await torController.onionAuthViewKeys();
+ if (keyInfoList) {
+ // Filter out temporary keys.
+ this._keyInfoList = keyInfoList.filter(aKeyInfo => {
+ if (!aKeyInfo.Flags) {
+ return false;
+ }
+
+ const flags = aKeyInfo.Flags.split(",");
+ return flags.includes("Permanent");
+ });
+
+ // Sort by the .onion address.
+ this._keyInfoList.sort((aObj1, aObj2) => {
+ const hsAddr1 = aObj1.hsAddress.toLowerCase();
+ const hsAddr2 = aObj2.hsAddress.toLowerCase();
+ if (hsAddr1 < hsAddr2) {
+ return -1;
+ }
+ return hsAddr1 > hsAddr2 ? 1 : 0;
+ });
+ }
+
+ // Render the tree content.
+ this._tree.rowCountChanged(0, this.rowCount);
+ } catch (e) {
+ if (e.torMessage) {
+ this._showError(e.torMessage);
+ } else {
+ this._showError(controllerFailureMsg);
+ }
+ }
+
+ this._setBusyState(false);
+ },
+
+ // This method may throw; callers should catch errors.
+ async _deleteOneKey(aTorController, aIndex) {
+ const keyInfoObj = this._keyInfoList[aIndex];
+ await aTorController.onionAuthRemove(keyInfoObj.hsAddress);
+ this._tree.view.selection.clearRange(aIndex, aIndex);
+ this._keyInfoList.splice(aIndex, 1);
+ this._tree.rowCountChanged(aIndex + 1, -1);
+ },
+
+ _setBusyState(aIsBusy) {
+ this._isBusy = aIsBusy;
+ this.updateButtonsState();
+ },
+
+ _onWindowKeyPress(event) {
+ if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) {
+ window.close();
+ } else if (event.keyCode === KeyEvent.DOM_VK_DELETE) {
+ this.deleteSelectedKeys();
+ }
+ },
+
+ _showError(aMessage) {
+ const dialog = document.querySelector(this.selector.dialog);
+ const errorIcon = dialog.querySelector(this.selector.errorIcon);
+ errorIcon.style.visibility = aMessage ? "visible" : "hidden";
+ const errorDesc = dialog.querySelector(this.selector.errorMessage);
+ errorDesc.textContent = aMessage ? aMessage : "";
+ },
+
+ // XUL tree widget view implementation.
+ get rowCount() {
+ return this._keyInfoList ? this._keyInfoList.length : 0;
+ },
+
+ getCellText(aRow, aCol) {
+ let val = "";
+ if (this._keyInfoList && aRow < this._keyInfoList.length) {
+ const keyInfo = this._keyInfoList[aRow];
+ if (aCol.id.endsWith("-siteCol")) {
+ val = keyInfo.hsAddress;
+ } else if (aCol.id.endsWith("-keyCol")) {
+ val = keyInfo.typeAndKey;
+ // Omit keyType because it is always "x25519".
+ const idx = val.indexOf(":");
+ if (idx > 0) {
+ val = val.substring(idx + 1);
+ }
+ }
+ }
+
+ return val;
+ },
+
+ isSeparator(index) {
+ return false;
+ },
+
+ isSorted() {
+ return false;
+ },
+
+ isContainer(index) {
+ return false;
+ },
+
+ setTree(tree) {},
+
+ getImageSrc(row, column) {},
+
+ getCellValue(row, column) {},
+
+ cycleHeader(column) {},
+
+ getRowProperties(row) {
+ return "";
+ },
+
+ getColumnProperties(column) {
+ return "";
+ },
+
+ getCellProperties(row, column) {
+ return "";
+ },
+};
+
+window.addEventListener("load", () => gOnionServicesSavedKeysDialog._onLoad());
diff --git a/browser/components/onionservices/content/savedKeysDialog.xhtml b/browser/components/onionservices/content/savedKeysDialog.xhtml
new file mode 100644
index 000000000000..3db9bb05ea82
--- /dev/null
+++ b/browser/components/onionservices/content/savedKeysDialog.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!-- Copyright (c) 2020, The Tor Project, Inc. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css" type="text/css"?>
+
+<window id="onionservices-savedkeys-dialog"
+ windowtype="OnionServices:SavedKeys"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 45em;">
+
+ <script src="chrome://browser/content/onionservices/savedKeysDialog.js"/>
+
+ <vbox id="onionservices-savedkeys" class="contentPane" flex="1">
+ <label id="onionservices-savedkeys-intro"
+ control="onionservices-savedkeys-tree"/>
+ <separator class="thin"/>
+ <tree id="onionservices-savedkeys-tree" flex="1" hidecolumnpicker="true"
+ width="750"
+ style="height: 20em;"
+ onselect="gOnionServicesSavedKeysDialog.updateButtonsState();">
+ <treecols>
+ <treecol id="onionservices-savedkeys-siteCol" flex="1" persist="width"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="onionservices-savedkeys-keyCol" flex="1" persist="width"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ <hbox id="onionservices-savedkeys-errorContainer" align="baseline" flex="1">
+ <image id="onionservices-savedkeys-errorIcon"/>
+ <description id="onionservices-savedkeys-errorMessage" flex="1"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox id="onionservices-savedkeys-buttons">
+ <button id="onionservices-savedkeys-remove" disabled="true"
+ oncommand="gOnionServicesSavedKeysDialog.deleteSelectedKeys();"/>
+ <button id="onionservices-savedkeys-removeall"
+ oncommand="gOnionServicesSavedKeysDialog.deleteAllKeys();"/>
+ </hbox>
+ </vbox>
+</window>
diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn
new file mode 100644
index 000000000000..9d6ce88d1841
--- /dev/null
+++ b/browser/components/onionservices/jar.mn
@@ -0,0 +1,9 @@
+browser.jar:
+ content/browser/onionservices/authPreferences.css (content/authPreferences.css)
+ content/browser/onionservices/authPreferences.js (content/authPreferences.js)
+ content/browser/onionservices/authPrompt.js (content/authPrompt.js)
+ content/browser/onionservices/authUtil.jsm (content/authUtil.jsm)
+ content/browser/onionservices/netError/ (content/netError/*)
+ content/browser/onionservices/onionservices.css (content/onionservices.css)
+ content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js)
+ content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml)
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
new file mode 100644
index 000000000000..7e103239c8d6
--- /dev/null
+++ b/browser/components/onionservices/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ['jar.mn']
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 514eba207ca3..2997149dbb05 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -12,6 +12,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
+<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css"?>
<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index d2a1eeba830e..c6ddcacd4e99 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -499,6 +499,8 @@
<label id="fips-desc" hidden="true" data-l10n-id="forms-master-pw-fips-desc"></label>
</groupbox>
+#include ../onionservices/content/authPreferences.inc.xhtml
+
<!-- The form autofill section is inserted in to this box
after the form autofill extension has initialized. -->
<groupbox id="formAutofillGroupBox"
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 9cd6d7260136..7f77b0aa6688 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -80,6 +80,12 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
}
});
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["OnionServicesAuthPreferences"],
+ "chrome://browser/content/onionservices/authPreferences.js"
+);
+
// TODO: module import via ChromeUtils.defineModuleGetter
XPCOMUtils.defineLazyScriptGetter(
this,
@@ -505,6 +511,7 @@ var gPrivacyPane = {
this.trackingProtectionReadPrefs();
this.networkCookieBehaviorReadPrefs();
this._initTrackingProtectionExtensionControl();
+ OnionServicesAuthPreferences.init();
this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css
index 83e2a6623cc7..785a929256fc 100644
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -120,6 +120,9 @@
list-style-image: url(chrome://browser/skin/notification-icons/indexedDB.svg);
}
+/* Reuse Firefox's login (key) icon for the Tor onion services auth. prompt */
+.popup-notification-icon[popupid="tor-clientauth"],
+.tor-clientauth-icon,
.popup-notification-icon[popupid="password"],
.login-icon {
list-style-image: url(chrome://browser/skin/login.svg);
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index 1c361875ec4c..1ef5ee496399 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3497,6 +3497,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
}
} else {
// Errors requiring simple formatting
+ bool isOnionAuthError = false;
switch (aError) {
case NS_ERROR_MALFORMED_URI:
// URI is malformed
@@ -3579,10 +3580,44 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
// HTTP/2 or HTTP/3 stack detected a protocol error
error = "networkProtocolError";
break;
-
+ case NS_ERROR_TOR_ONION_SVC_NOT_FOUND:
+ error = "onionServices.descNotFound";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_IS_INVALID:
+ error = "onionServices.descInvalid";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_INTRO_FAILED:
+ error = "onionServices.introFailed";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_REND_FAILED:
+ error = "onionServices.rendezvousFailed";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH:
+ error = "onionServices.clientAuthMissing";
+ isOnionAuthError = true;
+ break;
+ case NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH:
+ error = "onionServices.clientAuthIncorrect";
+ isOnionAuthError = true;
+ break;
+ case NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS:
+ error = "onionServices.badAddress";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT:
+ error = "onionServices.introTimedOut";
+ break;
default:
break;
}
+
+ // The presence of aFailedChannel indicates that we arrived here due to a
+ // failed connection attempt. Note that we will arrive here a second time
+ // if the user cancels the Tor client auth prompt, but in that case we
+ // will not have a failed channel and therefore we will not prompt again.
+ if (isOnionAuthError && aFailedChannel) {
+ // Display about:blank while the Tor client auth prompt is open.
+ errorPage.AssignLiteral("blank");
+ }
}
// If the HTTPS-Only Mode upgraded this request and the upgrade might have
@@ -3665,6 +3700,20 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
nsAutoString str;
rv =
stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str);
+ if (NS_FAILED(rv)) {
+ // As a fallback, check torbutton.properties for the error string.
+ const char bundleURL[] = "chrome://torbutton/locale/torbutton.properties";
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService) {
+ nsCOMPtr<nsIStringBundle> tbStringBundle;
+ if (NS_SUCCEEDED(stringBundleService->CreateBundle(
+ bundleURL, getter_AddRefs(tbStringBundle)))) {
+ rv = tbStringBundle->FormatStringFromName(errorDescriptionID,
+ formatStrs, str);
+ }
+ }
+ }
NS_ENSURE_SUCCESS(rv, rv);
messageStr.Assign(str);
}
@@ -6108,6 +6157,7 @@ nsresult nsDocShell::FilterStatusForErrorPage(
aStatus == NS_ERROR_FILE_ACCESS_DENIED ||
aStatus == NS_ERROR_CORRUPTED_CONTENT ||
aStatus == NS_ERROR_INVALID_CONTENT_ENCODING ||
+ NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_TOR ||
NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
// Errors to be shown for any frame
return aStatus;
@@ -7824,6 +7874,35 @@ nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType,
FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags);
}
+ // Arrange to show a Tor onion service client authentication prompt if
+ // appropriate.
+ if ((mLoadType == LOAD_ERROR_PAGE) && failedChannel) {
+ nsresult status = NS_OK;
+ if (NS_SUCCEEDED(failedChannel->GetStatus(&status)) &&
+ ((status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH) ||
+ (status == NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH))) {
+ nsAutoCString onionHost;
+ failedURI->GetHost(onionHost);
+ const char* topic = (status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH)
+ ? "tor-onion-services-clientauth-missing"
+ : "tor-onion-services-clientauth-incorrect";
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+ if (browserChild) {
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SendShowOnionServicesAuthPrompt(onionHost, nsCString(topic));
+ }
+ } else {
+ nsCOMPtr<nsPIDOMWindowOuter> browserWin = GetWindow();
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (browserWin && obsSvc) {
+ obsSvc->NotifyObservers(browserWin, topic,
+ NS_ConvertUTF8toUTF16(onionHost).get());
+ }
+ }
+ }
+ }
+
return NS_OK;
}
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 04935175f029..caedda626553 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -3908,6 +3908,27 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt(
return IPC_OK();
}
+mozilla::ipc::IPCResult BrowserParent::RecvShowOnionServicesAuthPrompt(
+ const nsCString& aOnionName, const nsCString& aTopic) {
+ nsCOMPtr<nsIBrowser> browser =
+ mFrameElement ? mFrameElement->AsBrowser() : nullptr;
+ if (!browser) {
+ // If the tab is being closed, the browser may not be available.
+ // In this case we can ignore the request.
+ return IPC_OK();
+ }
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsresult rv = os->NotifyObservers(browser, aTopic.get(),
+ NS_ConvertUTF8toUTF16(aOnionName).get());
+ if (NS_FAILED(rv)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
mozilla::ipc::IPCResult BrowserParent::RecvVisitURI(nsIURI* aURI,
nsIURI* aLastVisitedURI,
const uint32_t& aFlags) {
diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h
index 63a776755e26..87dba47d43a0 100644
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -761,6 +761,9 @@ class BrowserParent final : public PBrowserParent,
mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(
const nsCString& aOrigin, const bool& aHideDoorHanger);
+ mozilla::ipc::IPCResult RecvShowOnionServicesAuthPrompt(
+ const nsCString& aOnionName, const nsCString& aTopic);
+
mozilla::ipc::IPCResult RecvSetSystemFont(const nsCString& aFontName);
mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName);
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index bf9fefa00c9c..eb50dafd4406 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -596,6 +596,15 @@ parent:
bool aNeedCollectSHistory, uint32_t aFlushId,
bool aIsFinal, uint32_t aEpoch);
+ /**
+ * This function is used to notify the parent that it should display a
+ * onion services client authentication prompt.
+ *
+ * @param aOnionHost The hostname of the .onion that needs authentication.
+ * @param aTopic The reason for the prompt.
+ */
+ async ShowOnionServicesAuthPrompt(nsCString aOnionHost, nsCString aTopic);
+
child:
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
async FlushTabState(uint32_t aFlushId, bool aIsFinal);
diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg
index d884c6a85999..31e5e75ba35c 100644
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -253,5 +253,15 @@ XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinti
XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining")
XPC_MSG_DEF(NS_ERROR_SOCIALTRACKING_URI , "The URI is social tracking")
+/* Codes related to Tor */
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_NOT_FOUND , "Tor onion service descriptor cannot be found")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_IS_INVALID , "Tor onion service descriptor is invalid")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED , "Tor onion service introduction failed")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_REND_FAILED , "Tor onion service rendezvous failed")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH, "Tor onion service missing client authorization")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH , "Tor onion service wrong client authorization")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS , "Tor onion service bad address")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT , "Tor onion service introduction timed out")
+
/* Profile manager error codes */
XPC_MSG_DEF(NS_ERROR_DATABASE_CHANGED , "Flushing the profiles to disk would have overwritten changes made elsewhere.")
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
index dcf2a33b25f3..97716cf90522 100644
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -215,6 +215,12 @@ nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
default:
if (psm::IsNSSErrorCode(errorCode)) {
rv = psm::GetXPCOMFromNSSError(errorCode);
+ } else {
+ // If we received a Tor extended error code via SOCKS, pass it through.
+ nsresult res = nsresult(errorCode);
+ if (NS_ERROR_GET_MODULE(res) == NS_ERROR_MODULE_TOR) {
+ rv = res;
+ }
}
break;
diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp
index c8b42d142a53..5e16e186a37c 100644
--- a/netwerk/socket/nsSOCKSIOLayer.cpp
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -1007,6 +1007,55 @@ PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() {
"08, Address type not supported."));
c = PR_BAD_ADDRESS_ERROR;
break;
+ case 0xF0: // Tor SOCKS5_HS_NOT_FOUND
+ LOGERROR(
+ ("socks5: connect failed: F0,"
+ " Tor onion service descriptor can not be found."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_NOT_FOUND);
+ break;
+ case 0xF1: // Tor SOCKS5_HS_IS_INVALID
+ LOGERROR(
+ ("socks5: connect failed: F1,"
+ " Tor onion service descriptor is invalid."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_IS_INVALID);
+ break;
+ case 0xF2: // Tor SOCKS5_HS_INTRO_FAILED
+ LOGERROR(
+ ("socks5: connect failed: F2,"
+ " Tor onion service introduction failed."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED);
+ break;
+ case 0xF3: // Tor SOCKS5_HS_REND_FAILED
+ LOGERROR(
+ ("socks5: connect failed: F3,"
+ " Tor onion service rendezvous failed."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_REND_FAILED);
+ break;
+ case 0xF4: // Tor SOCKS5_HS_MISSING_CLIENT_AUTH
+ LOGERROR(
+ ("socks5: connect failed: F4,"
+ " Tor onion service missing client authorization."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH);
+ break;
+ case 0xF5: // Tor SOCKS5_HS_BAD_CLIENT_AUTH
+ LOGERROR(
+ ("socks5: connect failed: F5,"
+ " Tor onion service wrong client authorization."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH);
+ break;
+ case 0xF6: // Tor SOCKS5_HS_BAD_ADDRESS
+ LOGERROR(
+ ("socks5: connect failed: F6,"
+ " Tor onion service bad address."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS);
+ break;
+ case 0xF7: // Tor SOCKS5_HS_INTRO_TIMEDOUT
+ LOGERROR(
+ ("socks5: connect failed: F7,"
+ " Tor onion service introduction timed out."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT);
+ break;
+
default:
LOGERROR(("socks5: connect failed."));
break;
diff --git a/toolkit/modules/PopupNotifications.jsm b/toolkit/modules/PopupNotifications.jsm
index 2fc54a589969..68f687698cfa 100644
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -406,6 +406,8 @@ PopupNotifications.prototype = {
* will be dismissed instead of removed after running the callback.
* - [optional] disabled (boolean): If this is true, the button
* will be disabled.
+ * - [optional] leaveOpen (boolean): If this is true, the notification
+ * will not be removed after running the callback.
* - [optional] disableHighlight (boolean): If this is true, the button
* will not apply the default highlight style.
* If null, the notification will have a default "OK" action button
@@ -1890,6 +1892,10 @@ PopupNotifications.prototype = {
this._dismiss();
return;
}
+
+ if (action.leaveOpen) {
+ return;
+ }
}
this._remove(notification);
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index e6b37e77c31e..049e2ffd70ce 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -104,6 +104,7 @@ let RemotePageAccessManager = {
RPMPrefIsLocked: ["security.tls.version.min"],
RPMAddToHistogram: ["*"],
RPMGetHttpResponseHeader: ["*"],
+ RPMGetTorStrings: ["*"],
},
"about:newinstall": {
RPMGetUpdateChannel: ["*"],
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
index dd5c5fdbe1b0..464e0f654262 100644
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
@@ -38,5 +38,6 @@ module.exports = {
RPMAddToHistogram: false,
RPMRemoveMessageListener: false,
RPMGetHttpResponseHeader: false,
+ RPMGetTorStrings: false,
},
};
diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py
index 5451127bcffe..2086aeee3ba4 100755
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -85,6 +85,7 @@ modules["URL_CLASSIFIER"] = Mod(42)
# ErrorResult gets its own module to reduce the chance of someone accidentally
# defining an error code matching one of the ErrorResult ones.
modules["ERRORRESULT"] = Mod(43)
+modules["TOR"] = Mod(44)
# NS_ERROR_MODULE_GENERAL should be used by modules that do not
# care if return code values overlap. Callers of methods that
@@ -1179,6 +1180,27 @@ with modules["ERRORRESULT"]:
errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5)
+# =======================================================================
+# 44: Tor-specific error codes.
+# =======================================================================
+with modules["TOR"]:
+ # Tor onion service descriptor can not be found.
+ errors["NS_ERROR_TOR_ONION_SVC_NOT_FOUND"] = FAILURE(1)
+ # Tor onion service descriptor is invalid.
+ errors["NS_ERROR_TOR_ONION_SVC_IS_INVALID"] = FAILURE(2)
+ # Tor onion service introduction failed.
+ errors["NS_ERROR_TOR_ONION_SVC_INTRO_FAILED"] = FAILURE(3)
+ # Tor onion service rendezvous failed.
+ errors["NS_ERROR_TOR_ONION_SVC_REND_FAILED"] = FAILURE(4)
+ # Tor onion service missing client authorization.
+ errors["NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH"] = FAILURE(5)
+ # Tor onion service wrong client authorization.
+ errors["NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH"] = FAILURE(6)
+ # Tor onion service bad address.
+ errors["NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS"] = FAILURE(7)
+ # Tor onion service introduction timed out.
+ errors["NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT"] = FAILURE(8)
+
# =======================================================================
# 51: NS_ERROR_MODULE_GENERAL
# =======================================================================
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 24796 - Comment out excess permissions from GeckoView
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit fd2137abdfaeb7399de1019ee770fd99277c73ce
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Wed Apr 11 17:52:59 2018 +0000
Bug 24796 - Comment out excess permissions from GeckoView
The GeckoView AndroidManifest.xml is not preprocessed unlike Fennec's
manifest, so we can't use the ifdef preprocessor guards around the
permissions we do not want. Commenting the permissions is the
next-best-thing.
---
.../android/geckoview/src/main/AndroidManifest.xml | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/mobile/android/geckoview/src/main/AndroidManifest.xml b/mobile/android/geckoview/src/main/AndroidManifest.xml
index 87ad6dc28047..4c8ab2a9d996 100644
--- a/mobile/android/geckoview/src/main/AndroidManifest.xml
+++ b/mobile/android/geckoview/src/main/AndroidManifest.xml
@@ -6,20 +6,32 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mozilla.geckoview">
+<!--#ifdef MOZ_ANDROID_NETWORK_STATE-->
+ <!--
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ -->
+<!--#endif-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+<!--#ifdef MOZ_ANDROID_LOCATION-->
+ <!--
<uses-feature
android:name="android.hardware.location"
android:required="false"/>
<uses-feature
android:name="android.hardware.location.gps"
android:required="false"/>
+ -->
+<!--#endif-->
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
+<!--#ifdef MOZ_WEBRTC-->
+ <!-- TODO preprocess AndroidManifest.xml so that we can
+ conditionally include WebRTC permissions based on MOZ_WEBRTC. -->
+ <!--
<uses-feature
android:name="android.hardware.camera"
android:required="false"/>
@@ -28,14 +40,16 @@
android:required="false"/>
<uses-feature
- android:name="android.hardware.audio.low_latency"
+ android:name="android.hardware.camera.any"
android:required="false"/>
<uses-feature
- android:name="android.hardware.microphone"
+ android:name="android.hardware.audio.low_latency"
android:required="false"/>
<uses-feature
- android:name="android.hardware.camera.any"
+ android:name="android.hardware.microphone"
android:required="false"/>
+ -->
+<!--#endif-->
<!-- GeckoView requires OpenGL ES 2.0 -->
<uses-feature
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 32658: Create a new MAR signing key
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 1edd9cf80c43a133d6f6c97ea5ae5045860b8810
Author: Georg Koppen <gk(a)torproject.org>
Date: Fri Jan 17 12:54:31 2020 +0000
Bug 32658: Create a new MAR signing key
It's time for our rotation again: Move the backup key in the front
position and add a new backup key.
---
toolkit/mozapps/update/updater/release_primary.der | Bin 1225 -> 1229 bytes
toolkit/mozapps/update/updater/release_secondary.der | Bin 1225 -> 1229 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/toolkit/mozapps/update/updater/release_primary.der b/toolkit/mozapps/update/updater/release_primary.der
index 1d94f88ad73b..0103a171de88 100644
Binary files a/toolkit/mozapps/update/updater/release_primary.der and b/toolkit/mozapps/update/updater/release_primary.der differ
diff --git a/toolkit/mozapps/update/updater/release_secondary.der b/toolkit/mozapps/update/updater/release_secondary.der
index 474706c4b73c..fcee3944e9b7 100644
Binary files a/toolkit/mozapps/update/updater/release_secondary.der and b/toolkit/mozapps/update/updater/release_secondary.der differ
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 32220: Improve the letterboxing experience
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 679e0074a65b206b5f620f59fea14f1b1f3ec87f
Author: Richard Pospesel <richard(a)torproject.org>
Date: Mon Oct 28 17:42:17 2019 -0700
Bug 32220: Improve the letterboxing experience
CSS and JS changes to alter the UX surrounding letterboxing. The
browser element containing page content is now anchored to the bottom
of the toolbar, and the remaining letterbox margin is the same color
as the firefox chrome. The letterbox margin and border are tied to
the currently selected theme.
Also adds a 'needsLetterbox' property to tabbrowser.xml to fix a race
condition present when using the 'isEmpty' property. Using 'isEmpty'
as a proxy for 'needsLetterbox' resulted in over-zealous/unnecessary
letterboxing of about:blank tabs.
---
browser/base/content/browser.css | 8 ++
browser/base/content/tabbrowser-tab.js | 9 +++
browser/themes/shared/tabs.inc.css | 6 ++
.../components/resistfingerprinting/RFPHelper.jsm | 94 +++++++++++++++++++---
4 files changed, 105 insertions(+), 12 deletions(-)
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index 085e47cad9f0..07694dbd2c30 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -85,6 +85,14 @@ body {
display: none;
}
+
+.browserStack > browser.letterboxing {
+ border-color: var(--chrome-content-separator-color);
+ border-style: solid;
+ border-width : 1px;
+ border-top: none;
+}
+
%ifdef MENUBAR_CAN_AUTOHIDE
#toolbar-menubar[autohide="true"] {
overflow: hidden;
diff --git a/browser/base/content/tabbrowser-tab.js b/browser/base/content/tabbrowser-tab.js
index 183eff1bab86..7f376ab1d122 100644
--- a/browser/base/content/tabbrowser-tab.js
+++ b/browser/base/content/tabbrowser-tab.js
@@ -225,6 +225,15 @@
return true;
}
+ get needsLetterbox() {
+ let browser = this.linkedBrowser;
+ if (isBlankPageURL(browser.currentURI.spec)) {
+ return false;
+ }
+
+ return true;
+ }
+
get lastAccessed() {
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
}
diff --git a/browser/themes/shared/tabs.inc.css b/browser/themes/shared/tabs.inc.css
index b8a117532049..cf8f444b262d 100644
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -33,6 +33,12 @@
background-color: #f9f9fa;
}
+/* extend down the toolbar's colors when letterboxing is enabled*/
+#tabbrowser-tabpanels.letterboxing {
+ background-color: var(--toolbar-bgcolor);
+ background-image: var(--toolbar-bgimage);
+}
+
:root[privatebrowsingmode=temporary] #tabbrowser-tabpanels {
/* Value for --in-content-page-background in aboutPrivateBrowsing.css */
background-color: #25003e;
diff --git a/toolkit/components/resistfingerprinting/RFPHelper.jsm b/toolkit/components/resistfingerprinting/RFPHelper.jsm
index 166ad21e9013..9520d8720631 100644
--- a/toolkit/components/resistfingerprinting/RFPHelper.jsm
+++ b/toolkit/components/resistfingerprinting/RFPHelper.jsm
@@ -40,6 +40,7 @@ class _RFPHelper {
// ============================================================================
constructor() {
this._initialized = false;
+ this._borderDimensions = null;
}
init() {
@@ -361,6 +362,24 @@ class _RFPHelper {
});
}
+ getBorderDimensions(aBrowser) {
+ if (this._borderDimensions) {
+ return this._borderDimensions;
+ }
+
+ const win = aBrowser.ownerGlobal;
+ const browserStyle = win.getComputedStyle(aBrowser);
+
+ this._borderDimensions = {
+ top : parseInt(browserStyle.borderTopWidth),
+ right: parseInt(browserStyle.borderRightWidth),
+ bottom : parseInt(browserStyle.borderBottomWidth),
+ left : parseInt(browserStyle.borderLeftWidth),
+ };
+
+ return this._borderDimensions;
+ }
+
_addOrClearContentMargin(aBrowser) {
let tab = aBrowser.getTabBrowser().getTabForBrowser(aBrowser);
@@ -369,9 +388,13 @@ class _RFPHelper {
return;
}
+ // we add the letterboxing class even if the content does not need letterboxing
+ // in which case margins are set such that the borders are hidden
+ aBrowser.classList.add("letterboxing");
+
// We should apply no margin around an empty tab or a tab with system
// principal.
- if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) {
+ if (!tab.needsLetterbox || aBrowser.contentPrincipal.isSystemPrincipal) {
this._clearContentViewMargin(aBrowser);
} else {
this._roundContentView(aBrowser);
@@ -539,10 +562,29 @@ class _RFPHelper {
// Calculating the margins around the browser element in order to round the
// content viewport. We will use a 200x100 stepping if the dimension set
// is not given.
- let margins = calcMargins(containerWidth, containerHeight);
+
+ const borderDimensions = this.getBorderDimensions(aBrowser);
+ const marginDims = calcMargins(containerWidth, containerHeight - borderDimensions.top);
+
+ let margins = {
+ top : 0,
+ right : 0,
+ bottom : 0,
+ left : 0,
+ };
+
+ // snap browser element to top
+ margins.top = 0;
+ // and leave 'double' margin at the bottom
+ margins.bottom = 2 * marginDims.height - borderDimensions.bottom;
+ // identical margins left and right
+ margins.right = marginDims.width - borderDimensions.right;
+ margins.left = marginDims.width - borderDimensions.left;
+
+ const marginStyleString = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`;
// If the size of the content is already quantized, we do nothing.
- if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) {
+ if (aBrowser.style.margin === marginStyleString) {
log("_roundContentView[" + logId + "] is_rounded == true");
if (this._isLetterboxingTesting) {
log(
@@ -563,19 +605,35 @@ class _RFPHelper {
"_roundContentView[" +
logId +
"] setting margins to " +
- margins.width +
- " x " +
- margins.height
+ marginStyleString
);
- // One cannot (easily) control the color of a margin unfortunately.
- // An initial attempt to use a border instead of a margin resulted
- // in offset event dispatching; so for now we use a colorless margin.
- aBrowser.style.margin = `${margins.height}px ${margins.width}px`;
+
+ // The margin background color is determined by the background color of the
+ // window's tabpanels#tabbrowser-tabpanels element
+ aBrowser.style.margin = marginStyleString;
});
}
_clearContentViewMargin(aBrowser) {
+ const borderDimensions = this.getBorderDimensions(aBrowser);
+ // set the margins such that the browser elements border is visible up top, but
+ // are rendered off-screen on the remaining sides
+ let margins = {
+ top : 0,
+ right : -borderDimensions.right,
+ bottom : -borderDimensions.bottom,
+ left : -borderDimensions.left,
+ };
+ const marginStyleString = `${margins.top}px ${margins.right}px ${margins.bottom}px ${margins.left}px`;
+
+ aBrowser.ownerGlobal.requestAnimationFrame(() => {
+ aBrowser.style.margin = marginStyleString;
+ });
+ }
+
+ _removeLetterboxing(aBrowser) {
aBrowser.ownerGlobal.requestAnimationFrame(() => {
+ aBrowser.classList.remove("letterboxing");
aBrowser.style.margin = "";
});
}
@@ -593,6 +651,11 @@ class _RFPHelper {
aWindow.gBrowser.addTabsProgressListener(this);
aWindow.addEventListener("TabOpen", this);
+ const tabPanel = aWindow.document.getElementById("tabbrowser-tabpanels");
+ if (tabPanel) {
+ tabPanel.classList.add("letterboxing");
+ }
+
// Rounding the content viewport.
this._updateMarginsForTabsInWindow(aWindow);
}
@@ -616,10 +679,17 @@ class _RFPHelper {
tabBrowser.removeTabsProgressListener(this);
aWindow.removeEventListener("TabOpen", this);
- // Clear all margins and tooltip for all browsers.
+ // revert tabpanel's background colors to default
+ const tabPanel = aWindow.document.getElementById("tabbrowser-tabpanels");
+ if (tabPanel) {
+ tabPanel.classList.remove("letterboxing");
+ }
+
+ // and revert each browser element to default,
+ // restore default margins and remove letterboxing class
for (let tab of tabBrowser.tabs) {
let browser = tab.linkedBrowser;
- this._clearContentViewMargin(browser);
+ this._removeLetterboxing(browser);
}
}
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 25741 - TBA: Disable GeckoNetworkManager
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 29015fc3a2286ce5ad25576562a6296984ce9a3e
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Thu Apr 26 22:22:51 2018 +0000
Bug 25741 - TBA: Disable GeckoNetworkManager
The browser should not need information related to the network
interface or network state, tor should take care of that.
---
.../src/main/java/org/mozilla/geckoview/GeckoRuntime.java | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
index c908d23229d1..bd26f6782276 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -121,7 +121,9 @@ public final class GeckoRuntime implements Parcelable {
mPaused = false;
// Monitor network status and send change notifications to Gecko
// while active.
- GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext());
+ if (BuildConfig.TOR_BROWSER_VERSION == "") {
+ GeckoNetworkManager.getInstance().start(GeckoAppShell.getApplicationContext());
+ }
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@@ -129,7 +131,9 @@ public final class GeckoRuntime implements Parcelable {
Log.d(LOGTAG, "Lifecycle: onPause");
mPaused = true;
// Stop monitoring network status while inactive.
- GeckoNetworkManager.getInstance().stop();
+ if (BuildConfig.TOR_BROWSER_VERSION == "") {
+ GeckoNetworkManager.getInstance().stop();
+ }
GeckoThread.onPause();
}
}
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 7ada711033d398d5e4e1c92b7187d5b223983c58
Author: Amogh Pradeep <amoghbl1(a)gmail.com>
Date: Fri Jun 12 02:07:45 2015 -0400
Orfox: Centralized proxy applied to AbstractCommunicator and BaseResources.
See Bug 1357997 for partial uplift.
Also:
Bug 28051 - Use our Orbot for proxying our connections
Bug 31144 - ESR68 Network Code Review
---
.../main/java/org/mozilla/gecko/GeckoAppShell.java | 68 +++++++++++-----------
.../java/org/mozilla/gecko/util/BitmapUtils.java | 7 ---
.../java/org/mozilla/gecko/util/ProxySelector.java | 25 +++++++-
3 files changed, 59 insertions(+), 41 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
index 995b23316c32..b9ca73bee2eb 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -1764,39 +1764,41 @@ public class GeckoAppShell {
@WrapForJNI
private static URLConnection getConnection(final String url) {
- try {
- String spec;
- if (url.startsWith("android://")) {
- spec = url.substring(10);
- } else {
- spec = url.substring(8);
- }
-
- // Check if we are loading a package icon.
- try {
- if (spec.startsWith("icon/")) {
- String[] splits = spec.split("/");
- if (splits.length != 2) {
- return null;
- }
- final String pkg = splits[1];
- final PackageManager pm = getApplicationContext().getPackageManager();
- final Drawable d = pm.getApplicationIcon(pkg);
- final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d);
- return new BitmapConnection(bitmap);
- }
- } catch (Exception ex) {
- Log.e(LOGTAG, "error", ex);
- }
-
- // if the colon got stripped, put it back
- int colon = spec.indexOf(':');
- if (colon == -1 || colon > spec.indexOf('/')) {
- spec = spec.replaceFirst("/", ":/");
- }
- } catch (Exception ex) {
- return null;
- }
+ // Bug 31144 - Prevent potential proxy-bypass
+
+ //try {
+ // String spec;
+ // if (url.startsWith("android://")) {
+ // spec = url.substring(10);
+ // } else {
+ // spec = url.substring(8);
+ // }
+
+ // // Check if we are loading a package icon.
+ // try {
+ // if (spec.startsWith("icon/")) {
+ // String[] splits = spec.split("/");
+ // if (splits.length != 2) {
+ // return null;
+ // }
+ // final String pkg = splits[1];
+ // final PackageManager pm = getApplicationContext().getPackageManager();
+ // final Drawable d = pm.getApplicationIcon(pkg);
+ // final Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(d);
+ // return new BitmapConnection(bitmap);
+ // }
+ // } catch (Exception ex) {
+ // Log.e(LOGTAG, "error", ex);
+ // }
+
+ // // if the colon got stripped, put it back
+ // int colon = spec.indexOf(':');
+ // if (colon == -1 || colon > spec.indexOf('/')) {
+ // spec = spec.replaceFirst("/", ":/");
+ // }
+ //} catch (Exception ex) {
+ // return null;
+ //}
return null;
}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java
index 73a69a3abd66..f795dacffb47 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/BitmapUtils.java
@@ -101,13 +101,6 @@ public final class BitmapUtils {
public static Bitmap decodeUrl(final URL url) {
InputStream stream = null;
- try {
- stream = url.openStream();
- } catch (IOException e) {
- Log.w(LOGTAG, "decodeUrl: IOException downloading " + url);
- return null;
- }
-
if (stream == null) {
Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url);
return null;
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
index 3940d3c84249..9515975f680a 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ProxySelector.java
@@ -29,6 +29,10 @@ import java.net.URLConnection;
import java.util.List;
public class ProxySelector {
+ private static final String TOR_PROXY_ADDRESS = "127.0.0.1";
+ private static final int TOR_SOCKS_PROXY_PORT = 9150;
+ private static final int TOR_HTTP_PROXY_PORT = 8218;
+
public static URLConnection openConnectionWithProxy(final URI uri) throws IOException {
java.net.ProxySelector ps = java.net.ProxySelector.getDefault();
Proxy proxy = Proxy.NO_PROXY;
@@ -39,7 +43,26 @@ public class ProxySelector {
}
}
- return uri.toURL().openConnection(proxy);
+ /* Ignore the proxy we found from the VM, only use Tor. We can probably
+ * safely use the logic in this class in the future. */
+ return uri.toURL().openConnection(getProxy());
+ }
+
+ public static Proxy getProxy() {
+ // TODO make configurable
+ return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(TOR_PROXY_ADDRESS, TOR_SOCKS_PROXY_PORT));
+ }
+
+ public static String getProxyHostAddress() {
+ return TOR_PROXY_ADDRESS;
+ }
+
+ public static int getSocksProxyPort() {
+ return TOR_SOCKS_PROXY_PORT;
+ }
+
+ public static int getHttpProxyPort() {
+ return TOR_HTTP_PROXY_PORT;
}
public ProxySelector() {
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 28125 - Prevent non-Necko network connections
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit 3d15e14a4c8f965e4027e2fa4563aaaef1478ed1
Author: Matthew Finkel <Matthew.Finkel(a)gmail.com>
Date: Thu Oct 25 19:17:09 2018 +0000
Bug 28125 - Prevent non-Necko network connections
---
.../gecko/media/GeckoMediaDrmBridgeV21.java | 49 +---------------------
.../exoplayer2/upstream/DefaultHttpDataSource.java | 47 ++-------------------
2 files changed, 4 insertions(+), 92 deletions(-)
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
index 3ba59bfd6776..eb57b1013642 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
@@ -488,54 +488,7 @@ public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
@Override
protected Void doInBackground(final Void... params) {
- HttpURLConnection urlConnection = null;
- BufferedReader in = null;
- try {
- URI finalURI = new URI(mURL + "&signedRequest=" + URLEncoder.encode(new String(mDrmRequest), "UTF-8"));
- urlConnection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(finalURI);
- urlConnection.setRequestMethod("POST");
- if (DEBUG) Log.d(LOGTAG, "Provisioning, posting url =" + finalURI.toString());
-
- // Add data
- urlConnection.setRequestProperty("Accept", "*/*");
- urlConnection.setRequestProperty("User-Agent", getCDMUserAgent());
- urlConnection.setRequestProperty("Content-Type", "application/json");
-
- // Execute HTTP Post Request
- urlConnection.connect();
-
- int responseCode = urlConnection.getResponseCode();
- if (responseCode == HttpURLConnection.HTTP_OK) {
- in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8));
- String inputLine;
- StringBuffer response = new StringBuffer();
-
- while ((inputLine = in.readLine()) != null) {
- response.append(inputLine);
- }
- in.close();
- mResponseBody = String.valueOf(response).getBytes(StringUtils.UTF_8);
- if (DEBUG) Log.d(LOGTAG, "Provisioning, response received.");
- if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length);
- } else {
- Log.d(LOGTAG, "Provisioning, server returned HTTP error code :" + responseCode);
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Got exception during posting provisioning request ...", e);
- } catch (URISyntaxException e) {
- Log.e(LOGTAG, "Got exception during creating uri ...", e);
- } finally {
- if (urlConnection != null) {
- urlConnection.disconnect();
- }
- try {
- if (in != null) {
- in.close();
- }
- } catch (IOException e) {
- Log.e(LOGTAG, "Exception during closing in ...", e);
- }
- }
+ Log.i(LOGTAG, "This is Tor Browser. Skipping.");
return null;
}
diff --git a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
index 6e5095b0a4c9..a585e283ed4e 100644
--- a/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
+++ b/mobile/android/geckoview/src/thirdparty/java/org/mozilla/thirdparty/com/google/android/exoplayer2/upstream/DefaultHttpDataSource.java
@@ -46,6 +46,7 @@ import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.mozilla.gecko.util.ProxySelector;
+
/**
* An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}.
*
@@ -516,50 +517,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
boolean followRedirects,
Map<String, String> requestParameters)
throws IOException, URISyntaxException {
- /**
- * Tor Project modified the way the connection object was created. For the sake of
- * simplicity, instead of duplicating the whole file we changed the connection object
- * to use the ProxySelector.
- */
- HttpURLConnection connection = (HttpURLConnection) ProxySelector.openConnectionWithProxy(url.toURI());
-
- connection.setConnectTimeout(connectTimeoutMillis);
- connection.setReadTimeout(readTimeoutMillis);
-
- Map<String, String> requestHeaders = new HashMap<>();
- if (defaultRequestProperties != null) {
- requestHeaders.putAll(defaultRequestProperties.getSnapshot());
- }
- requestHeaders.putAll(requestProperties.getSnapshot());
- requestHeaders.putAll(requestParameters);
-
- for (Map.Entry<String, String> property : requestHeaders.entrySet()) {
- connection.setRequestProperty(property.getKey(), property.getValue());
- }
-
- if (!(position == 0 && length == C.LENGTH_UNSET)) {
- String rangeRequest = "bytes=" + position + "-";
- if (length != C.LENGTH_UNSET) {
- rangeRequest += (position + length - 1);
- }
- connection.setRequestProperty("Range", rangeRequest);
- }
- connection.setRequestProperty("User-Agent", userAgent);
- connection.setRequestProperty("Accept-Encoding", allowGzip ? "gzip" : "identity");
- connection.setInstanceFollowRedirects(followRedirects);
- connection.setDoOutput(httpBody != null);
- connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod));
-
- if (httpBody != null) {
- connection.setFixedLengthStreamingMode(httpBody.length);
- connection.connect();
- OutputStream os = connection.getOutputStream();
- os.write(httpBody);
- os.close();
- } else {
- connection.connect();
- }
- return connection;
+ Log.i(TAG, "This is Tor Browser. Skipping.");
+ throw new IOException();
}
/** Creates an {@link HttpURLConnection} that is connected with the {@code url}. */
1
0

[tor-browser/tor-browser-81.0b9-10.0-1] Bug 33342: Avoid disconnect search addon error after removal.
by gk@torproject.org 15 Sep '20
by gk@torproject.org 15 Sep '20
15 Sep '20
commit cc8d4b318446549c33b71bc3f11725efb17ee705
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Mar 13 18:19:30 2020 +0100
Bug 33342: Avoid disconnect search addon error after removal.
We removed the addon in #32767, but it was still being loaded
from addonStartup.json.lz4 and throwing an error on startup
because its resource: location is not available anymore.
---
toolkit/mozapps/extensions/internal/XPIProvider.jsm | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 78c0b0f7c702..ad8922dadebf 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -970,6 +970,12 @@ var BuiltInLocation = new (class _BuiltInLocation extends XPIStateLocation {
get enumerable() {
return false;
}
+
+ restore(saved) {
+ super.restore(saved);
+ // Bug 33342: avoid restoring disconnect addon from addonStartup.json.lz4.
+ this.removeAddon("disconnect(a)search.mozilla.org");
+ }
})();
/**
1
0