[tbb-commits] [tor-browser] 01/21: Bug 10760: Integrate TorButton to TorBrowser core
gitolite role
git at cupani.torproject.org
Tue Dec 6 08:31:57 UTC 2022
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-102.5.0esr-12.5-1
in repository tor-browser.
commit e1eb5ce81dc8f0bd786597415a0069c3f603c74c
Author: Alex Catarineu <acat at torproject.org>
AuthorDate: Wed Feb 19 23:05:08 2020 +0100
Bug 10760: Integrate TorButton to TorBrowser core
Because of the non-restartless nature of Torbutton, it required
a two-stage installation process. On mobile, it was a problem,
because it was not loading when the user opened the browser for
the first time.
Moving it to tor-browser and making it a system extension allows it
to load when the user opens the browser for first time.
Additionally, this patch also fixes Bug 27611.
Bug 26321: New Circuit and New Identity menu items
Bug 14392: Make about:tor behave like other initial pages.
Bug 25013: Add torbutton as a tor-browser submodule
Bug 31575: Replace Firefox Home (newtab) with about:tor
Avoid loading AboutNewTab in BrowserGlue.jsm in order
to avoid several network requests that we do not need. Besides,
about:newtab will now point to about:blank or about:tor (depending
on browser.newtabpage.enabled) and about:home will point to
about:tor.
---
browser/base/content/aboutDialog.xhtml | 40 +-
browser/base/content/appmenu-viewcache.inc.xhtml | 11 +-
browser/base/content/browser-doctype.inc | 8 +
browser/base/content/browser-menubar.inc | 24 +-
browser/base/content/browser-sets.inc | 1 +
browser/base/content/browser.js | 1 +
browser/base/content/browser.xhtml | 13 +
browser/base/content/hiddenWindowMac.xhtml | 4 +
browser/base/content/navigator-toolbox.inc.xhtml | 5 +
browser/base/content/pageinfo/pageInfo.xhtml | 6 +
browser/components/BrowserGlue.jsm | 33 +-
.../controlcenter/content/identityPanel.inc.xhtml | 22 +
browser/components/newtab/AboutNewTabService.jsm | 15 +-
browser/components/places/content/places.xhtml | 3 +
browser/components/preferences/home.inc.xhtml | 4 +-
browser/components/preferences/preferences.xhtml | 5 +-
.../shell/content/setDesktopBackground.xhtml | 6 +
browser/installer/package-manifest.in | 2 +
browser/modules/HomePage.jsm | 2 +-
browser/themes/shared/icons/new_circuit.svg | 6 +
browser/themes/shared/jar.inc.mn | 2 +
browser/themes/shared/toolbarbutton-icons.css | 4 +
docshell/base/nsAboutRedirector.cpp | 6 +-
docshell/build/components.conf | 1 +
mobile/android/installer/package-manifest.in | 4 +
toolkit/moz.build | 1 +
toolkit/torbutton/.gitignore | 1 +
toolkit/torbutton/CREDITS | 5 +
toolkit/torbutton/LICENSE | 29 +
.../chrome/content/aboutTor/aboutTor-content.js | 139 +++
.../chrome/content/aboutTor/aboutTor.xhtml | 112 +++
.../chrome/content/aboutTor/resources/aboutTor.js | 11 +
.../torbutton/chrome/content/preferences-mobile.js | 59 ++
toolkit/torbutton/chrome/content/preferences.xhtml | 84 ++
.../chrome/content/tor-circuit-display.js | 604 +++++++++++
toolkit/torbutton/chrome/content/torbutton.js | 1044 ++++++++++++++++++++
toolkit/torbutton/chrome/skin/about-wordmark.png | Bin 0 -> 4609 bytes
toolkit/torbutton/chrome/skin/aboutDialog.css | 34 +
toolkit/torbutton/chrome/skin/aboutTor.css | 313 ++++++
toolkit/torbutton/chrome/skin/banner-warning.svg | 1 +
toolkit/torbutton/chrome/skin/dax-logo.svg | 1 +
toolkit/torbutton/chrome/skin/icon-newsletter.png | Bin 0 -> 649 bytes
.../torbutton/chrome/skin/preferences-mobile.css | 47 +
toolkit/torbutton/chrome/skin/preferences.css | 7 +
.../torbutton/chrome/skin/tor-circuit-display.css | 193 ++++
.../chrome/skin/tor-circuit-line-first.svg | 6 +
.../chrome/skin/tor-circuit-line-last.svg | 6 +
toolkit/torbutton/chrome/skin/tor-circuit-line.svg | 7 +
toolkit/torbutton/chrome/skin/tor.png | Bin 0 -> 2073 bytes
.../chrome/skin/torbrowser_mobile_logo.png | Bin 0 -> 9345 bytes
toolkit/torbutton/chrome/skin/torbutton.css | 14 +
toolkit/torbutton/chrome/skin/torbutton.svg | 3 +
toolkit/torbutton/components/domain-isolator.js | 228 +++++
toolkit/torbutton/components/dragDropFilter.js | 134 +++
.../torbutton/components/external-app-blocker.js | 160 +++
toolkit/torbutton/components/startup-observer.js | 196 ++++
toolkit/torbutton/components/torCheckService.js | 140 +++
toolkit/torbutton/components/torbutton-logger.js | 185 ++++
toolkit/torbutton/jar.mn | 46 +
toolkit/torbutton/modules/tor-control-port.js | 982 ++++++++++++++++++
toolkit/torbutton/modules/utils.js | 318 ++++++
toolkit/torbutton/moz.build | 6 +
.../lib/environments/browser-window.js | 6 +-
63 files changed, 5284 insertions(+), 66 deletions(-)
diff --git a/browser/base/content/aboutDialog.xhtml b/browser/base/content/aboutDialog.xhtml
index 90a568a17dd6..60b1e15b637c 100644
--- a/browser/base/content/aboutDialog.xhtml
+++ b/browser/base/content/aboutDialog.xhtml
@@ -7,6 +7,12 @@
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/aboutDialog.css" type="text/css"?>
<?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://torbutton/skin/aboutDialog.css" type="text/css"?>
+
+<!-- We need to include the localization DTDs until we migrate to Fluent -->
+<!DOCTYPE window [
+#include browser-doctype.inc
+]>
<window xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
@@ -22,7 +28,7 @@
data-l10n-id="aboutDialog-title"
#endif
role="dialog"
- aria-describedby="version distribution distributionId communityDesc contributeDesc trademark"
+ aria-describedby="version distribution distributionId projectDesc helpDesc trademark trademarkTor"
>
#ifdef XP_MACOSX
#include macWindow.inc.xhtml
@@ -140,24 +146,36 @@
<label is="text-link" useoriginprincipal="true" href="about:credits" data-l10n-name="community-exp-creditsLink"/>
</description>
</vbox>
- <description class="text-blurb" id="communityDesc" data-l10n-id="community-2">
- <label is="text-link" href="https://www.mozilla.org/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-name="community-mozillaLink"/>
- <label is="text-link" useoriginprincipal="true" href="about:credits" data-l10n-name="community-creditsLink"/>
+ <!-- Keep communityDesc and contributeDesc to avoid JS errors trying to hide them -->
+ <description class="text-blurb" id="communityDesc" data-l10n-id="community-2" hidden="true"></description>
+ <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus" hidden="true"></description>
+ <description class="text-blurb" id="projectDesc">
+ &project.start;
+ <label is="text-link" href="https://www.torproject.org/">
+ &project.tpoLink;
+ </label>&project.end;
</description>
- <description class="text-blurb" id="contributeDesc" data-l10n-id="helpus">
- <label is="text-link" href="https://donate.mozilla.org/?utm_source=firefox&utm_medium=referral&utm_campaign=firefox_about&utm_content=firefox_about" data-l10n-name="helpus-donateLink"/>
- <label is="text-link" href="https://www.mozilla.org/contribute/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-name="helpus-getInvolvedLink"/>
+ <description class="text-blurb" id="helpDesc">
+ &help.start;
+ <label is="text-link" href="https://donate.torproject.org/">
+ &help.donateLink;
+ </label>
+ &help.or;
+ <label is="text-link" href="https://community.torproject.org/">
+ &help.getInvolvedLink;
+ </label>&help.end;
</description>
</vbox>
</vbox>
</hbox>
<vbox id="bottomBox">
- <hbox pack="center">
- <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license" data-l10n-id="bottomLinks-license"/>
- <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:rights" data-l10n-id="bottomLinks-rights"/>
- <label is="text-link" class="bottom-link" href="https://www.mozilla.org/privacy/?utm_source=firefox-browser&utm_medium=firefox-desktop&utm_campaign=about-dialog" data-l10n-id="bottomLinks-privacy"/>
+ <hbox id="newBottom" pack="center" position="1">
+ <label is="text-link" class="bottom-link" href="https://support.torproject.org/">&bottomLinks.questions;</label>
+ <label is="text-link" class="bottom-link" href="https://community.torproject.org/relay/">&bottomLinks.grow;</label>
+ <label is="text-link" class="bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label>
</hbox>
<description id="trademark" data-l10n-id="trademarkInfo"></description>
+ <description id="trademarkTor">&tor.TrademarkStatement;</description>
</vbox>
</vbox>
diff --git a/browser/base/content/appmenu-viewcache.inc.xhtml b/browser/base/content/appmenu-viewcache.inc.xhtml
index c138be3ee86c..cb685a3d0f52 100644
--- a/browser/base/content/appmenu-viewcache.inc.xhtml
+++ b/browser/base/content/appmenu-viewcache.inc.xhtml
@@ -55,11 +55,17 @@
class="subviewbutton"
data-l10n-id="appmenuitem-new-private-window"
key="key_privatebrowsing"
- command="Tools:PrivateBrowsing"/>
+ command="Tools:PrivateBrowsing"
+ hidden="true"/>
<toolbarseparator/>
<toolbarbutton id="appMenu-new-identity"
class="subviewbutton"
key="new-identity-key"/>
+ <toolbarbutton id="appMenuNewCircuit"
+ class="subviewbutton"
+ key="torbutton-new-circuit-key"
+ label="&torbutton.context_menu.new_circuit_sentence_case;"
+ oncommand="torbutton_new_circuit();"/>
<toolbarseparator/>
<toolbarbutton id="appMenu-bookmarks-button"
class="subviewbutton subviewbutton-nav"
@@ -179,7 +185,8 @@
<toolbarbutton id="appMenu-restoreSession"
data-l10n-id="appmenu-restore-session"
class="subviewbutton"
- command="Browser:RestoreLastSession"/>
+ command="Browser:RestoreLastSession"
+ hidden="true"/>
<toolbarseparator/>
<toolbarbutton id="appMenuClearRecentHistory"
data-l10n-id="appmenu-clear-history"
diff --git a/browser/base/content/browser-doctype.inc b/browser/base/content/browser-doctype.inc
new file mode 100644
index 000000000000..fdc302e79de3
--- /dev/null
+++ b/browser/base/content/browser-doctype.inc
@@ -0,0 +1,8 @@
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % torbuttonDTD SYSTEM "chrome://torbutton/locale/torbutton.dtd">
+%torbuttonDTD;
+<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
+%aboutTorDTD;
+<!ENTITY % aboutDialogDTD SYSTEM "chrome://torbutton/locale/aboutDialog.dtd">
+%aboutDialogDTD;
diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc
index 7c3dd4fe6d25..cde4d205b763 100644
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -31,6 +31,11 @@
<menuseparator/>
<menuitem id="menu_newIdentity"
key="new-identity-key"/>
+ <menuitem id="menu_newCircuit"
+ accesskey="&torbutton.context_menu.new_circuit_key;"
+ key="torbutton-new-circuit-key"
+ label="&torbutton.context_menu.new_circuit;"
+ oncommand="torbutton_new_circuit();"/>
<menuseparator/>
<menuitem id="menu_openLocation"
hidden="true"
@@ -456,8 +461,20 @@
<menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();">
<!-- Note: Items under here are cloned to the AppMenu Help submenu. The cloned items
have their strings defined by appmenu-data-l10n-id. -->
+ <!-- dummy elements to avoid 'getElementById' errors -->
+ <box id="feedbackPage"/>
+ <box id="helpSafeMode"/>
+ <box id="menu_HelpPopup_reportPhishingtoolmenu"/>
+ <box id="menu_HelpPopup_reportPhishingErrortoolmenu"/>
+ <!-- Add Tor Browser manual link -->
+ <menuitem id="torBrowserUserManual"
+ oncommand="gBrowser.selectedTab = gBrowser.addTab('about:manual', {triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal()});"
+ label="&aboutTor.torbrowser_user_manual.label;"
+ accesskey="&aboutTor.torbrowser_user_manual.accesskey;"/>
+ <!-- Bug 18905: Hide unused help menu items -->
<menuitem id="menu_openHelp"
oncommand="openHelpLink('firefox-help')"
+ hidden="true"
data-l10n-id="menu-get-help"
appmenu-data-l10n-id="appmenu-get-help"
#ifdef XP_MACOSX
@@ -467,14 +484,17 @@
#endif
<menuitem id="feedbackPage"
oncommand="openFeedbackPage()"
- data-l10n-id="menu-help-share-ideas"
- appmenu-data-l10n-id="appmenu-help-share-ideas"/>
+ hidden="true"
+ data-l10n-id="menu-help-feedback-page"
+ appmenu-data-l10n-id="appmenu-help-feedback-page"/>
<menuitem id="helpSafeMode"
oncommand="safeModeRestart();"
+ hidden="true"
data-l10n-id="menu-help-enter-troubleshoot-mode2"
appmenu-data-l10n-id="appmenu-help-enter-troubleshoot-mode2"/>
<menuitem id="troubleShooting"
oncommand="openTroubleshootingPage()"
+ hidden="true"
data-l10n-id="menu-help-more-troubleshooting-info"
appmenu-data-l10n-id="appmenu-help-more-troubleshooting-info"/>
<menuitem id="help_reportSiteIssue"
diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc
index 17edb35baf6a..dc5d93978950 100644
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -389,4 +389,5 @@
internal="true"/>
#endif
<key id="new-identity-key" modifiers="accel shift" key="U" oncommand="NewIdentityButton.onCommand(event)"/>
+ <key id="torbutton-new-circuit-key" modifiers="accel shift" key="L" oncommand="torbutton_new_circuit()"/>
</keyset>
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index ee392d55b572..597bff687349 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -645,6 +645,7 @@ var gPageIcons = {
};
var gInitialPages = [
+ "about:tor",
"about:blank",
"about:home",
...(AppConstants.NIGHTLY_BUILD ? ["about:firefoxview"] : []),
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index df33143a7365..5959615f9dc1 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -35,6 +35,12 @@
<?xml-stylesheet href="chrome://browser/skin/searchbar.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/places/tree-icons.css" type="text/css"?>
<?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"?>
+
+<!DOCTYPE window [
+#include browser-doctype.inc
+]>
<html id="main-window"
xmlns:html="http://www.w3.org/1999/xhtml"
@@ -116,11 +122,18 @@
Services.scriptloader.loadSubScript("chrome://browser/content/search/autocomplete-popup.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/search/searchbar.js", this);
Services.scriptloader.loadSubScript("chrome://browser/content/languageNotification.js", this);
+ Services.scriptloader.loadSubScript("chrome://torbutton/content/tor-circuit-display.js", this);
+ Services.scriptloader.loadSubScript("chrome://torbutton/content/torbutton.js", this);
window.onload = gBrowserInit.onLoad.bind(gBrowserInit);
window.onunload = gBrowserInit.onUnload.bind(gBrowserInit);
window.onclose = WindowIsClosing;
+ //onLoad Handler
+ try {
+ window.addEventListener("load", torbutton_init, false);
+ } catch (e) {}
+
window.addEventListener("MozBeforeInitialXULLayout",
gBrowserInit.onBeforeInitialXULLayout.bind(gBrowserInit), { once: true });
diff --git a/browser/base/content/hiddenWindowMac.xhtml b/browser/base/content/hiddenWindowMac.xhtml
index c27d394d3707..b272a076bb30 100644
--- a/browser/base/content/hiddenWindowMac.xhtml
+++ b/browser/base/content/hiddenWindowMac.xhtml
@@ -8,6 +8,10 @@
<?xml-stylesheet href="chrome://browser/skin/webRTC-menubar-indicator.css" type="text/css"?>
+<!DOCTYPE window [
+#include browser-doctype.inc
+]>
+
<window id="main-window"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index cadf68c91679..6123e1336aed 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -539,6 +539,11 @@
<toolbarbutton id="new-identity-button" class="toolbarbutton-1 chromeclass-toolbar-additional"/>
+ <toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&torbutton.context_menu.new_circuit;"
+ oncommand="torbutton_new_circuit();"
+ tooltiptext="&torbutton.context_menu.new_circuit;"/>
+
<toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
observes="View:FullScreen"
type="checkbox"
diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml
index 4fe7f4909180..b2b09f753902 100644
--- a/browser/base/content/pageinfo/pageInfo.xhtml
+++ b/browser/base/content/pageinfo/pageInfo.xhtml
@@ -6,6 +6,12 @@
<?xml-stylesheet href="chrome://browser/content/pageinfo/pageInfo.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/pageInfo.css" type="text/css"?>
+<!DOCTYPE window [
+#ifdef XP_MACOSX
+#include ../browser-doctype.inc
+#endif
+]>
+
<window id="main-window"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 99b289bd934a..6d0947a464ba 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -18,7 +18,6 @@ const { AppConstants } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
- AboutNewTab: "resource:///modules/AboutNewTab.jsm",
ActorManagerParent: "resource://gre/modules/ActorManagerParent.jsm",
AddonManager: "resource://gre/modules/AddonManager.jsm",
AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
@@ -225,28 +224,6 @@ let JSWINDOWACTORS = {
remoteTypes: ["privilegedabout"],
},
- AboutNewTab: {
- parent: {
- moduleURI: "resource:///actors/AboutNewTabParent.jsm",
- },
- child: {
- moduleURI: "resource:///actors/AboutNewTabChild.jsm",
- events: {
- DOMContentLoaded: {},
- pageshow: {},
- visibilitychange: {},
- },
- },
- // The wildcard on about:newtab is for the ?endpoint query parameter
- // that is used for snippets debugging. The wildcard for about:home
- // is similar, and also allows for falling back to loading the
- // about:home document dynamically if an attempt is made to load
- // about:home?jscache from the AboutHomeStartupCache as a top-level
- // load.
- matches: ["about:home*", "about:welcome", "about:newtab*"],
- remoteTypes: ["privilegedabout"],
- },
-
AboutPlugins: {
parent: {
moduleURI: "resource:///actors/AboutPluginsParent.jsm",
@@ -1599,8 +1576,6 @@ BrowserGlue.prototype = {
// the first browser window has finished initializing
_onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
- AboutNewTab.init();
-
TabCrashHandler.init();
ProcessHangMonitor.init();
@@ -5792,12 +5767,8 @@ var AboutHomeStartupCache = {
return { pageInputStream: null, scriptInputStream: null };
}
- let state = AboutNewTab.activityStream.store.getState();
- return new Promise(resolve => {
- this._cacheDeferred = resolve;
- this.log.trace("Parent is requesting cache streams.");
- this._procManager.sendAsyncMessage(this.CACHE_REQUEST_MESSAGE, { state });
- });
+ this.log.error("Activity Stream is disabled in Tor Browser.");
+ return { pageInputStream: null, scriptInputStream: null };
},
/**
diff --git a/browser/components/controlcenter/content/identityPanel.inc.xhtml b/browser/components/controlcenter/content/identityPanel.inc.xhtml
index ad6f9db340ef..498f374ffde8 100644
--- a/browser/components/controlcenter/content/identityPanel.inc.xhtml
+++ b/browser/components/controlcenter/content/identityPanel.inc.xhtml
@@ -92,6 +92,28 @@
</vbox>
</hbox>
+ <!-- Circuit display section -->
+
+ <vbox id="circuit-display-container" class="identity-popup-section">
+ <toolbarseparator/>
+ <vbox id="circuit-display-header" flex="1" role="group"
+ aria-labelledby="circuit-display-headline">
+ <hbox flex="1">
+ <label id="circuit-display-headline"
+ role="heading" aria-level="2">&torbutton.circuit_display.title;</label>
+ </hbox>
+ </vbox>
+ <vbox id="circuit-display-content">
+ <html:ul id="circuit-display-nodes" dir="auto"/>
+ <hbox id="circuit-guard-note-container"/>
+ <hbox id="circuit-reload-button-container">
+ <html:button id="circuit-reload-button"
+ onclick="torbutton_new_circuit()"
+ default="true">&torbutton.circuit_display.new_circuit;</html:button>
+ </hbox>
+ </vbox>
+ </vbox>
+
<!-- Clear Site Data Button -->
<vbox hidden="true"
id="identity-popup-clear-sitedata-footer">
diff --git a/browser/components/newtab/AboutNewTabService.jsm b/browser/components/newtab/AboutNewTabService.jsm
index f3bc40019f8f..471a3139baa7 100644
--- a/browser/components/newtab/AboutNewTabService.jsm
+++ b/browser/components/newtab/AboutNewTabService.jsm
@@ -420,20 +420,7 @@ class BaseAboutNewTabService {
* the newtab page has no effect on the result of this function.
*/
get defaultURL() {
- // Generate the desired activity stream resource depending on state, e.g.,
- // "resource://activity-stream/prerendered/activity-stream.html"
- // "resource://activity-stream/prerendered/activity-stream-debug.html"
- // "resource://activity-stream/prerendered/activity-stream-noscripts.html"
- return [
- "resource://activity-stream/prerendered/",
- "activity-stream",
- // Debug version loads dev scripts but noscripts separately loads scripts
- this.activityStreamDebug && !this.privilegedAboutProcessEnabled
- ? "-debug"
- : "",
- this.privilegedAboutProcessEnabled ? "-noscripts" : "",
- ".html",
- ].join("");
+ return "about:tor";
}
get welcomeURL() {
diff --git a/browser/components/places/content/places.xhtml b/browser/components/places/content/places.xhtml
index e19a58029828..f55d813664f6 100644
--- a/browser/components/places/content/places.xhtml
+++ b/browser/components/places/content/places.xhtml
@@ -18,6 +18,9 @@
<!DOCTYPE window [
<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
%editMenuOverlayDTD;
+#ifdef XP_MACOSX
+#include ../../../base/content/browser-doctype.inc
+#endif
]>
<window id="places"
diff --git a/browser/components/preferences/home.inc.xhtml b/browser/components/preferences/home.inc.xhtml
index 5bb936782ed9..e812d969837e 100644
--- a/browser/components/preferences/home.inc.xhtml
+++ b/browser/components/preferences/home.inc.xhtml
@@ -33,7 +33,7 @@
class="check-home-page-controlled"
data-preference-related="browser.startup.homepage">
<menupopup>
- <menuitem value="0" data-l10n-id="home-mode-choice-default" />
+ <menuitem value="0" label="&aboutTor.title;" />
<menuitem value="2" data-l10n-id="home-mode-choice-custom" />
<menuitem value="1" data-l10n-id="home-mode-choice-blank" />
</menupopup>
@@ -84,7 +84,7 @@
Preferences so we need to handle setting the pref manually.-->
<menulist id="newTabMode" flex="1" data-preference-related="browser.newtabpage.enabled">
<menupopup>
- <menuitem value="0" data-l10n-id="home-mode-choice-default" />
+ <menuitem value="0" label="&aboutTor.title;" />
<menuitem value="1" data-l10n-id="home-mode-choice-blank" />
</menupopup>
</menulist>
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 8706870466fa..f1a8115843a3 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -15,7 +15,10 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
-<!DOCTYPE html>
+<!DOCTYPE html [
+<!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
+ %aboutTorDTD;
+]>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:html="http://www.w3.org/1999/xhtml"
diff --git a/browser/components/shell/content/setDesktopBackground.xhtml b/browser/components/shell/content/setDesktopBackground.xhtml
index a7d4ced792b6..ef102450ff2c 100644
--- a/browser/components/shell/content/setDesktopBackground.xhtml
+++ b/browser/components/shell/content/setDesktopBackground.xhtml
@@ -7,6 +7,12 @@
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/setDesktopBackground.css" type="text/css"?>
+<!DOCTYPE window [
+#ifdef XP_MACOSX
+#include ../../../base/content/browser-doctype.inc
+#endif
+]>
+
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
windowtype="Shell:SetDesktopBackground"
diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in
index cb7891d5b43e..04acc38fa5cf 100644
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -235,6 +235,8 @@
@RESPATH@/chrome/pdfjs.manifest
@RESPATH@/chrome/pdfjs/*
@RESPATH@/components/tor-launcher.manifest
+ at RESPATH@/chrome/torbutton.manifest
+ at RESPATH@/chrome/torbutton/*
@RESPATH@/chrome/toolkit at JAREXT@
@RESPATH@/chrome/toolkit.manifest
#ifdef MOZ_GTK
diff --git a/browser/modules/HomePage.jsm b/browser/modules/HomePage.jsm
index f73b0f3e6c8c..26618374df3a 100644
--- a/browser/modules/HomePage.jsm
+++ b/browser/modules/HomePage.jsm
@@ -21,7 +21,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
});
const kPrefName = "browser.startup.homepage";
-const kDefaultHomePage = "about:home";
+const kDefaultHomePage = "about:tor";
const kExtensionControllerPref =
"browser.startup.homepage_override.extensionControlled";
const kHomePageIgnoreListId = "homepage-urls";
diff --git a/browser/themes/shared/icons/new_circuit.svg b/browser/themes/shared/icons/new_circuit.svg
new file mode 100644
index 000000000000..ddc819946818
--- /dev/null
+++ b/browser/themes/shared/icons/new_circuit.svg
@@ -0,0 +1,6 @@
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g stroke="none" stroke-width="1" fill="context-fill" fill-rule="evenodd" opacity="context-fill-opacity">
+ <path d="m10.707 6h3.993l.3-.3v-3.993c.0002-.09902-.0291-.19586-.084-.27825s-.1331-.14661-.2245-.18453c-.0915-.03792-.1922-.04782-.2893-.02845-.0971.01936-.1863.06713-.2562.13723l-1.459 1.459c-1.2817-1.16743-2.95335-1.813714-4.687-1.812-3.859 0-7 3.141-7 7s3.141 7 7 7c1.74123.007 3.422-.6379 4.7116-1.8079 1.2896-1.1701 2.0945-2.7804 2.2564-4.5141.0156-.1649-.0348-.32927-.1401-.4571s-.2571-.2087-.4219-.2249c-.1644-.01324-.3275.03801-.4548.1429s-.2088.2552-.2272.4191c-.1334 1.42392 [...]
+ <path d="m8 12.5c-2.48528 0-4.5-2.0147-4.5-4.5 0-2.48528 2.01472-4.5 4.5-4.5z"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index d04f95e59ea0..95fe3fdbe299 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -266,3 +266,5 @@
skin/classic/browser/syncedtabs/sidebar.css (../shared/syncedtabs/sidebar.css)
skin/classic/browser/new_identity.svg (../shared/icons/new_identity.svg)
+
+ skin/classic/browser/new_circuit.svg (../shared/icons/new_circuit.svg)
diff --git a/browser/themes/shared/toolbarbutton-icons.css b/browser/themes/shared/toolbarbutton-icons.css
index 8e285fdfd7c2..b2cb4d277e1e 100644
--- a/browser/themes/shared/toolbarbutton-icons.css
+++ b/browser/themes/shared/toolbarbutton-icons.css
@@ -267,6 +267,10 @@ toolbar {
list-style-image: url("chrome://browser/skin/new_identity.svg");
}
+#new-circuit-button {
+ list-style-image: url("chrome://browser/skin/new_circuit.svg");
+}
+
#privatebrowsing-button {
list-style-image: url("chrome://browser/skin/privateBrowsing.svg");
}
diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp
index e28bec9bd2c2..232104214844 100644
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -174,7 +174,11 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::HIDE_FROM_ABOUTABOUT |
nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
nsIAboutModule::URI_MUST_LOAD_IN_CHILD},
- {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}};
+ {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"tor", "chrome://torbutton/content/aboutTor/aboutTor.xhtml",
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI}};
static const int kRedirTotal = mozilla::ArrayLength(kRedirMap);
NS_IMETHODIMP
diff --git a/docshell/build/components.conf b/docshell/build/components.conf
index 5f11df641e37..6bc8617c8f0a 100644
--- a/docshell/build/components.conf
+++ b/docshell/build/components.conf
@@ -29,6 +29,7 @@ about_pages = [
'srcdoc',
'support',
'telemetry',
+ 'tor',
'url-classifier',
'webrtc',
]
diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in
index d9e8407ade7a..ead4a138d927 100644
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -137,6 +137,10 @@
; Base Browser
@BINPATH@/components/SecurityLevel.manifest
+; Torbutton
+ at BINPATH@/chrome/torbutton at JAREXT@
+ at BINPATH@/chrome/torbutton.manifest
+
; [Default Preferences]
; All the pref files must be part of base to prevent migration bugs
#ifndef MOZ_ANDROID_FAT_AAR_ARCHITECTURES
diff --git a/toolkit/moz.build b/toolkit/moz.build
index d464f7eb8092..cf9f7202ece0 100644
--- a/toolkit/moz.build
+++ b/toolkit/moz.build
@@ -22,6 +22,7 @@ DIRS += [
"mozapps/preferences",
"profile",
"themes",
+ "torbutton",
]
if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["MOZ_DEFAULT_BROWSER_AGENT"]:
diff --git a/toolkit/torbutton/.gitignore b/toolkit/torbutton/.gitignore
new file mode 100644
index 000000000000..753ac2e10e5a
--- /dev/null
+++ b/toolkit/torbutton/.gitignore
@@ -0,0 +1 @@
+translation
diff --git a/toolkit/torbutton/CREDITS b/toolkit/torbutton/CREDITS
new file mode 100644
index 000000000000..c2a0be95196a
--- /dev/null
+++ b/toolkit/torbutton/CREDITS
@@ -0,0 +1,5 @@
+TorButton was adapted by Scott Squires from ProxyButton
+(proxybutton.mozdev.com), which was written by Oleg Ivanov.
+
+Security components by Mike Perry with History blocking and cookie jar code
+from Collin Jackson.
diff --git a/toolkit/torbutton/LICENSE b/toolkit/torbutton/LICENSE
new file mode 100644
index 000000000000..783d6a4705a1
--- /dev/null
+++ b/toolkit/torbutton/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2020, The Tor Project, Inc.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ (1) Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ (2) Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ (3)The name of the author may not be used to
+ endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/toolkit/torbutton/chrome/content/aboutTor/aboutTor-content.js b/toolkit/torbutton/chrome/content/aboutTor/aboutTor-content.js
new file mode 100644
index 000000000000..6c53dff832e5
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/aboutTor/aboutTor-content.js
@@ -0,0 +1,139 @@
+/*************************************************************************
+ * Copyright (c) 2019, The Tor Project, Inc.
+ * See LICENSE for licensing information.
+ *
+ * vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+ *
+ * about:tor content script
+ *************************************************************************/
+
+/*
+ * The following about:tor IPC messages are exchanged by this code and
+ * the code in torbutton.js:
+ * AboutTor:Loaded page loaded content -> chrome
+ * AboutTor:ChromeData privileged data chrome -> content
+ */
+
+/* globals content, addMessageListener, sendAsyncMessage,
+ removeMessageListener */
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const { bindPrefAndInit, getLocale } = ChromeUtils.import(
+ "resource://torbutton/modules/utils.js"
+);
+
+var AboutTorListener = {
+ kAboutTorLoadedMessage: "AboutTor:Loaded",
+ kAboutTorChromeDataMessage: "AboutTor:ChromeData",
+
+ get isAboutTor() {
+ return content.document.documentURI.toLowerCase() == "about:tor";
+ },
+
+ init(aChromeGlobal) {
+ aChromeGlobal.addEventListener("AboutTorLoad", this, false, true);
+ },
+
+ handleEvent(aEvent) {
+ if (!this.isAboutTor) {
+ return;
+ }
+
+ switch (aEvent.type) {
+ case "AboutTorLoad":
+ this.onPageLoad();
+ break;
+ case "pagehide":
+ this.onPageHide();
+ break;
+ }
+ },
+
+ receiveMessage(aMessage) {
+ if (!this.isAboutTor) {
+ return;
+ }
+
+ switch (aMessage.name) {
+ case this.kAboutTorChromeDataMessage:
+ this.onChromeDataUpdate(aMessage.data);
+ break;
+ }
+ },
+
+ onPageLoad() {
+ // Arrange to update localized text and links.
+ bindPrefAndInit("intl.locale.requested", () => {
+ this.onLocaleChange();
+ });
+
+ // Add message and event listeners.
+ addMessageListener(this.kAboutTorChromeDataMessage, this);
+ addEventListener("pagehide", this, false);
+ addEventListener("resize", this, false);
+
+ sendAsyncMessage(this.kAboutTorLoadedMessage);
+ },
+
+ onPageHide() {
+ removeEventListener("resize", this, false);
+ removeEventListener("pagehide", this, false);
+ removeMessageListener(this.kAboutTorChromeDataMessage, this);
+ },
+
+ onChromeDataUpdate(aData) {
+ let body = content.document.body;
+
+ // Update status: tor on/off, Tor Browser manual shown.
+ if (aData.torOn) {
+ body.setAttribute("toron", "yes");
+ } else {
+ body.removeAttribute("toron");
+ }
+
+ if (aData.updateChannel) {
+ body.setAttribute("updatechannel", aData.updateChannel);
+ } else {
+ body.removeAttribute("updatechannel");
+ }
+
+ if (aData.hasBeenUpdated) {
+ body.setAttribute("hasbeenupdated", "yes");
+ content.document
+ .getElementById("update-infolink")
+ .setAttribute("href", aData.updateMoreInfoURL);
+ }
+
+ if (aData.mobile) {
+ body.setAttribute("mobile", "yes");
+ }
+
+ // Setting body.initialized="yes" displays the body.
+ body.setAttribute("initialized", "yes");
+ },
+
+ onLocaleChange() {
+ // Set localized "Get Involved" link.
+ content.document.getElementById("getInvolvedLink").href =
+ `https://community.torproject.org/${getLocale()}`;
+
+ // Display the Tor Browser product name and version.
+ try {
+ const kBrandBundle = "chrome://branding/locale/brand.properties";
+ let brandBundle = Services.strings.createBundle(kBrandBundle);
+ let productName = brandBundle.GetStringFromName("brandFullName");
+ let tbbVersion = Services.prefs.getCharPref("torbrowser.version");
+ let elem = content.document.getElementById("torbrowser-version");
+
+ while (elem.firstChild) {
+ elem.firstChild.remove();
+ }
+ elem.appendChild(
+ content.document.createTextNode(productName + " " + tbbVersion)
+ );
+ } catch (e) {}
+ },
+};
+
+AboutTorListener.init(this);
diff --git a/toolkit/torbutton/chrome/content/aboutTor/aboutTor.xhtml b/toolkit/torbutton/chrome/content/aboutTor/aboutTor.xhtml
new file mode 100644
index 000000000000..e48c22630150
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/aboutTor/aboutTor.xhtml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ - Copyright (c) 2020, The Tor Project, Inc.
+ - See LICENSE for licensing information.
+ - vim: set sw=2 sts=2 ts=8 et syntax=xml:
+ -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutTorDTD SYSTEM "chrome://torbutton/locale/aboutTor.dtd">
+ %aboutTorDTD;
+ <!ENTITY % tbUpdateDTD SYSTEM "chrome://browser/locale/aboutTBUpdate.dtd">
+ %tbUpdateDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Security-Policy" content="default-src resource:; object-src 'none'" />
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+ <title>&aboutTor.title;</title>
+ <link rel="stylesheet" href="chrome://browser/skin/onionPattern.css" type="text/css" media="all" />
+ <link rel="stylesheet" href="resource://torbutton-assets/aboutTor.css" type="text/css" media="all" />
+ <script type="text/javascript" src="resource://torbutton-abouttor/aboutTor.js"></script>
+</head>
+<body dir="&locale.dir;">
+ <div class="torcontent-container">
+ <div id="torbrowser-info">
+ <div id="torbrowser-version"/>
+ <div id="torbrowser-changelog-link"><a href="about:tbupdate">&aboutTor.viewChangelog.label;</a></div>
+ </div>
+ <img class="torcontent-logo" src="resource://torbutton-assets/torbrowser_mobile_logo.png"/>
+ <div id="torstatus" class="top">
+ <div class="hideIfTorOff hideIfHasBeenUpdated torstatus-container">
+#if MOZ_UPDATE_CHANNEL == alpha
+ <div class="heading1">&aboutTor.alpha.ready.label;</div>
+ <br/>
+ <div class="heading2">&aboutTor.alpha.ready2.label;</div>
+#elif MOZ_UPDATE_CHANNEL == nightly
+ <div class="heading1">&aboutTor.nightly.ready.label;</div>
+ <br/>
+ <div class="heading2">&aboutTor.nightly.ready2.label;</div>
+#else
+ <div class="heading1">&aboutTor.ready.label;</div>
+ <br/>
+ <div class="heading2">&aboutTor.ready2.label;</div>
+#endif
+ </div>
+ <div class="showIfHasBeenUpdated torstatus-container">
+ <div class="heading1">&aboutTBUpdate.updated;</div>
+ <br/>
+ <div class="heading2">&aboutTBUpdate.linkPrefix;<a id="update-infolink">&aboutTBUpdate.linkLabel;</a>&aboutTBUpdate.linkSuffix;</div>
+ </div>
+ <div class="hideIfTorOn torstatus-container">
+ <div class="heading1">&aboutTor.failure.label;</div>
+ <br/>
+ <div class="heading2">&aboutTor.failure2.label;</div>
+ </div>
+ </div>
+
+ <div class="searchbox hideIfTorOff"> <!-- begin form based search -->
+ <form action="&aboutTor.searchDDGPost.link;" method="get">
+ <div class="searchwrapper">
+ <label class="searchlabel" for="search-text"></label>
+ <input name="q" id="search-text" placeholder="&aboutTor.search.label;"
+ autocomplete="off" type="text"/>
+ <input id="search-button" value=""
+ title="&aboutTor.search.label;"
+ alt="&aboutTor.search.label;" type="submit"/>
+ </div>
+ </form>
+ </div>
+
+#if MOZ_UPDATE_CHANNEL != release
+ <div id="bannerRoot">
+ <img id="bannerImg" src="resource://torbutton-assets/banner-warning.svg" />
+ <div id="bannerTextCol">
+ <!--<div id="bannerHeader"></div>
+ <br />-->
+#if MOZ_UPDATE_CHANNEL == alpha
+ <div id="bannerDescription">&aboutTor.alpha.bannerDescription;</div>
+ <br />
+ <a id="bannerLink" target="_blank" href="https://forum.torproject.net/">&aboutTor.alpha.bannerLink;</a>
+#else
+ <div id="bannerDescription">&aboutTor.nightly.bannerDescription;</div>
+ <br />
+ <a id="bannerLink" target="_blank" href="https://forum.torproject.net/">&aboutTor.nightly.bannerLink;</a>
+#endif
+ </div>
+ </div>
+#endif
+
+ <div id="bottom">
+ <p id="donate" class="moreInfoLink">&aboutTor.donationBanner.freeToUse;
+ <a href="https://donate.torproject.org/" target="_blank">&aboutTor.donationBanner.buttonA; »</a>
+ </p>
+ <p id="manual" class="showForManual moreInfoLink">&aboutTor.torbrowser_user_manual_questions.label;
+ <a id="manualLink" href="about:manual" target="_blank">&aboutTor.torbrowser_user_manual_link.label;</a></p>
+ <p id="newsletter" class="moreInfoLink"><img class="imageStyle" src="chrome://browser/skin/mail.svg"/>&aboutTor.newsletter.tagline;<br/>
+ <a href="https://newsletter.torproject.org">&aboutTor.newsletter.link_text; »</a>
+ </p>
+ <p id="mission">&aboutTor.tor_mission.label;
+ <a id="getInvolvedLink">&aboutTor.getInvolved.label;</a></p>
+ </div>
+ </div>
+#include ../../../../../browser/themes/shared/onionPattern.inc.xhtml
+</body>
+</html>
diff --git a/toolkit/torbutton/chrome/content/aboutTor/resources/aboutTor.js b/toolkit/torbutton/chrome/content/aboutTor/resources/aboutTor.js
new file mode 100644
index 000000000000..6687390b6aca
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/aboutTor/resources/aboutTor.js
@@ -0,0 +1,11 @@
+/*************************************************************************
+ * Copyright (c) 2020, The Tor Project, Inc.
+ * See LICENSE for licensing information.
+ *
+ * vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+ *************************************************************************/
+
+window.addEventListener("pageshow", function() {
+ let evt = new CustomEvent("AboutTorLoad", { bubbles: true });
+ document.dispatchEvent(evt);
+});
diff --git a/toolkit/torbutton/chrome/content/preferences-mobile.js b/toolkit/torbutton/chrome/content/preferences-mobile.js
new file mode 100644
index 000000000000..b696ef17fd32
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/preferences-mobile.js
@@ -0,0 +1,59 @@
+// # Security Settings User Interface for Mobile
+
+// Utilities
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {
+ getBoolPref,
+ getIntPref,
+ setBoolPref,
+ setIntPref,
+ getCharPref,
+} = Services.prefs;
+
+// Description elements have the follow names.
+const descNames = ["", "desc_standard", "desc_safer", "desc_safest"];
+// "Learn-more"-elements have the follow names.
+const linkNames = ["", "link_standard", "link_safer", "link_safest"];
+// A single `state` object that reflects the user settings in this UI.
+
+let state = { slider: 0, custom: false };
+
+// Utility functions to convert between the legacy 4-value pref index
+// and the 3-valued security slider.
+let sliderPositionToPrefSetting = pos => [0, 4, 2, 1][pos];
+let prefSettingToSliderPosition = pref => [0, 3, 2, 2, 1][pref];
+
+// Set the desired slider value and update UI.
+function torbutton_set_slider(sliderValue) {
+ state.slider = sliderValue;
+ let slider = document.getElementById("torbutton_sec_slider");
+ slider.value = sliderValue.toString();
+ let descs = descNames.map(name => document.getElementById(name));
+ descs.forEach((desc, i) => {
+ if (state.slider !== i) {
+ desc.style.display = "none";
+ } else {
+ desc.style.display = "block";
+ }
+ });
+ torbutton_save_security_settings();
+}
+
+// Read prefs 'browser.security_level.security_slider' and
+// 'browser.security_level.security_custom', and initialize the UI.
+function torbutton_init_security_ui() {
+ torbutton_set_slider(
+ prefSettingToSliderPosition(
+ getIntPref("browser.security_level.security_slider")
+ )
+ );
+}
+
+// Write the two prefs from the current settings.
+function torbutton_save_security_settings() {
+ setIntPref(
+ "browser.security_level.security_slider",
+ sliderPositionToPrefSetting(state.slider)
+ );
+ setBoolPref("browser.security_level.security_custom", state.custom);
+}
diff --git a/toolkit/torbutton/chrome/content/preferences.xhtml b/toolkit/torbutton/chrome/content/preferences.xhtml
new file mode 100644
index 000000000000..eafbf01e7d6c
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/preferences.xhtml
@@ -0,0 +1,84 @@
+<!DOCTYPE overlay SYSTEM "chrome://torbutton/locale/torbutton.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <meta charset="UTF-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+ <head>
+ <title>&torbutton.prefs.security_settings;</title>
+ <link type="text/css" rel="stylesheet" charset="UTF-8" href="chrome://torbutton/skin/preferences.css"/>
+ <link type="text/css" rel="stylesheet" charset="UTF-8" href="chrome://torbutton/skin/preferences-mobile.css"/>
+ <script type="text/javascript" src="preferences-mobile.js"></script>
+ <style>
+ </style>
+ </head>
+ <body onload="torbutton_init_security_ui()">
+ <div class="wrapper outer-wrapper">
+ <div class="title">
+ &torbutton.prefs.sec_caption;
+ </div>
+ <div class="wrapper inner-wrapper">
+ <input id="torbutton_sec_slider" type="range" min="1" max="3" list="datalist" onchange="torbutton_set_slider(parseInt(this.value, 10))"/>
+ <datalist id="datalist">
+ <option onclick="torbutton_set_slider(1)">
+ &torbutton.prefs.sec_standard_label;
+ </option>
+ <option onclick="torbutton_set_slider(2)">
+ &torbutton.prefs.sec_safer_label;
+ </option>
+ <option onclick="torbutton_set_slider(3)">
+ &torbutton.prefs.sec_safest_label;
+ </option>
+ </datalist>
+ <div class="description-wrapper">
+ <div id="desc_safest" class="description">
+ <p class="slider-text-size slider-text-weight">
+ &torbutton.prefs.sec_safest_description;
+ </p>
+ <p class="slider-text-size slider-text-weight">
+ &torbutton.prefs.sec_safest_list_label;
+ </p>
+ <p class="slider-text-size">
+ &torbutton.prefs.sec_js_disabled;
+ </p>
+ <p class="slider-text-size">
+ &torbutton.prefs.sec_limit_graphics_and_typography;
+ </p>
+ <p class="slider-text-size">
+ &torbutton.prefs.sec_click_to_play_media;
+ </p>
+ <a id="link_safest" class="text-link">
+ &torbutton.prefs.sec_learn_more_label;
+ </a>
+ </div>
+ <div id="desc_safer" class="description">
+ <p class="slider-text-size slider-text-weight">
+ &torbutton.prefs.sec_safer_description;
+ </p>
+ <p class="slider-text-size slider-text-weight">
+ &torbutton.prefs.sec_safer_list_label;
+ </p>
+ <p class="slider-text-size">
+ &torbutton.prefs.sec_js_on_https_sites_only;
+ </p>
+ <p class="slider-text-size">
+ &torbutton.prefs.sec_limit_typography;
+ </p>
+ <p class="slider-text-size">
+ &torbutton.prefs.sec_click_to_play_media;
+ </p>
+ <a id="link_safer" class="text-link">
+ &torbutton.prefs.sec_learn_more_label;
+ </a>
+ </div>
+ <div id="desc_standard" class="description">
+ <p class="slider-text-size slider-text-weight">
+ &torbutton.prefs.sec_standard_description;
+ </p>
+ <a id="link_standard" class="text-link">
+ &torbutton.prefs.sec_learn_more_label;
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/toolkit/torbutton/chrome/content/tor-circuit-display.js b/toolkit/torbutton/chrome/content/tor-circuit-display.js
new file mode 100644
index 000000000000..76fb74546ac6
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/tor-circuit-display.js
@@ -0,0 +1,604 @@
+// A script that automatically displays the Tor Circuit used for the
+// current domain for the currently selected tab.
+//
+// This file is written in call stack order (later functions
+// call earlier functions). The file can be processed
+// with docco.js to produce pretty documentation.
+//
+// This script is to be embedded in torbutton.xhtml. It defines a single global
+// function, createTorCircuitDisplay(), which activates the automatic Tor
+// circuit display for the current tab and any future tabs.
+//
+// See https://trac.torproject.org/8641
+
+/* jshint esnext: true */
+/* global document, gBrowser, Components */
+
+// ### Main function
+// __createTorCircuitDisplay(enablePrefName)__.
+// The single function that prepares tor circuit display. Connects to a tor
+// control port using information provided to the control port module via
+// a previous call to configureControlPortModule(), and binds to a named
+// bool pref whose value determines whether the circuit display is enabled
+// or disabled.
+let createTorCircuitDisplay = (function() {
+ "use strict";
+
+ // Mozilla utilities
+ const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
+ );
+
+ // Import the controller code.
+ const { wait_for_controller } = ChromeUtils.import(
+ "resource://torbutton/modules/tor-control-port.js"
+ );
+
+ // Utility functions
+ let {
+ bindPrefAndInit,
+ observe,
+ getLocale,
+ getDomainForBrowser,
+ torbutton_get_property_string,
+ } = ChromeUtils.import("resource://torbutton/modules/utils.js");
+
+ // Make the TorButton logger available.
+ let logger = Cc["@torproject.org/torbutton-logger;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+
+ // ## Circuit/stream credentials and node monitoring
+
+ // A mutable map that stores the current nodes for each
+ // SOCKS username/password pair.
+ let credentialsToNodeDataMap = new Map(),
+ // A mutable map that reports `true` for IDs of "mature" circuits
+ // (those that have conveyed a stream).
+ knownCircuitIDs = new Map(),
+ // A mutable map that records the SOCKS credentials for the
+ // latest channels for each browser + domain.
+ browserToCredentialsMap = new Map();
+
+ // __trimQuotes(s)__.
+ // Removes quotation marks around a quoted string.
+ let trimQuotes = s => (s ? s.match(/^"(.*)"$/)[1] : undefined);
+
+ // __getBridge(id)__.
+ // Gets the bridge parameters for a given node ID. If the node
+ // is not currently used as a bridge, returns null.
+ let getBridge = async function(controller, id) {
+ let bridges = await controller.getConf("bridge");
+ if (bridges) {
+ for (let bridge of bridges) {
+ if (bridge.ID && bridge.ID.toUpperCase() === id.toUpperCase()) {
+ return bridge;
+ }
+ }
+ }
+ return null;
+ };
+
+ // nodeDataForID(controller, id)__.
+ // Returns the type, IP addresses and country code of a node with given ID.
+ // Example: `nodeDataForID(controller, "20BC91DC525C3DC9974B29FBEAB51230DE024C44")`
+ // => `{ type: "default", ipAddrs: ["12.23.34.45", "2001:db8::"], countryCode: "fr" }`
+ let nodeDataForID = async function(controller, id) {
+ let result = { ipAddrs: [] };
+ const bridge = await getBridge(controller, id); // type, ip, countryCode;
+ const addrRe = /^\[?([^\]]+)\]?:\d+$/;
+ if (bridge) {
+ result.type = "bridge";
+ result.bridgeType = bridge.type;
+ // Attempt to get an IP address from bridge address string.
+ try {
+ const ip = bridge.address.match(addrRe)[1];
+ if (!ip.startsWith("0.")) {
+ result.ipAddrs = [ip];
+ }
+ } catch (e) {}
+ } else {
+ // either dealing with a relay, or a bridge whose fingerprint is not saved in torrc
+ try {
+ const statusMap = await controller.getInfo("ns/id/" + id);
+ result.type = "default";
+ if (!statusMap.IP.startsWith("0.")) {
+ result.ipAddrs.push(statusMap.IP);
+ }
+ try {
+ result.ipAddrs.push(statusMap.IPv6.match(addrRe)[1]);
+ } catch (e) {}
+ } catch (e) {
+ // getInfo will throw if the given id is not a relay
+ // this probably means we are dealing with a user-provided bridge with no fingerprint
+ result.type = "bridge";
+ // we don't know the ip/ipv6 or type, so leave blank
+ result.ipAddrs = [];
+ result.bridgeType = "";
+ }
+ }
+ if (result.ipAddrs.length) {
+ // Get the country code for the node's IP address.
+ try {
+ const countryCode = await controller.getInfo(
+ "ip-to-country/" + result.ipAddrs[0]
+ );
+ result.countryCode = countryCode === "??" ? null : countryCode;
+ } catch (e) {}
+ }
+ return result;
+ };
+
+ // __nodeDataForCircuit(controller, circuitEvent)__.
+ // Gets the information for a circuit.
+ let nodeDataForCircuit = async function(controller, circuitEvent) {
+ let rawIDs = circuitEvent.circuit.map(circ => circ[0]),
+ // Remove the leading '$' if present.
+ ids = rawIDs.map(id => (id[0] === "$" ? id.substring(1) : id));
+ // Get the node data for all IDs in circuit.
+ return Promise.all(ids.map(id => nodeDataForID(controller, id)));
+ };
+
+ // __getCircuitStatusByID(aController, circuitID)__
+ // Returns the circuit status for the circuit with the given ID.
+ let getCircuitStatusByID = async function(aController, circuitID) {
+ let circuitStatuses = await aController.getInfo("circuit-status");
+ if (circuitStatuses) {
+ for (let circuitStatus of circuitStatuses) {
+ if (circuitStatus.id === circuitID) {
+ return circuitStatus;
+ }
+ }
+ }
+ return null;
+ };
+
+ // __collectIsolationData(aController, updateUI)__.
+ // Watches for STREAM SENTCONNECT events. When a SENTCONNECT event occurs, then
+ // we assume isolation settings (SOCKS username+password) are now fixed for the
+ // corresponding circuit. Whenever the first stream on a new circuit is seen,
+ // looks up u+p and records the node data in the credentialsToNodeDataMap.
+ // We need to update the circuit display immediately after any new node data
+ // is received. So the `updateUI` callback will be called at that point.
+ // See https://trac.torproject.org/projects/tor/ticket/15493
+ let collectIsolationData = function(aController, updateUI) {
+ return aController.watchEvent(
+ "STREAM",
+ streamEvent => streamEvent.StreamStatus === "SENTCONNECT",
+ async streamEvent => {
+ if (!knownCircuitIDs.get(streamEvent.CircuitID)) {
+ logger.eclog(3, "streamEvent.CircuitID: " + streamEvent.CircuitID);
+ knownCircuitIDs.set(streamEvent.CircuitID, true);
+ let circuitStatus = await getCircuitStatusByID(
+ aController,
+ streamEvent.CircuitID
+ ),
+ credentials = circuitStatus
+ ? trimQuotes(circuitStatus.SOCKS_USERNAME) +
+ "|" +
+ trimQuotes(circuitStatus.SOCKS_PASSWORD)
+ : null;
+ if (credentials) {
+ let nodeData = await nodeDataForCircuit(aController, circuitStatus);
+ credentialsToNodeDataMap.set(credentials, nodeData);
+ updateUI();
+ }
+ }
+ }
+ );
+ };
+
+ // __browserForChannel(channel)__.
+ // Returns the browser that loaded a given channel.
+ let browserForChannel = function(channel) {
+ if (!channel) {
+ return null;
+ }
+ let chan = channel.QueryInterface(Ci.nsIChannel);
+ let callbacks = chan.notificationCallbacks;
+ if (!callbacks) {
+ return null;
+ }
+ let loadContext;
+ try {
+ loadContext = callbacks.getInterface(Ci.nsILoadContext);
+ } catch (e) {
+ // Ignore
+ return null;
+ }
+ if (!loadContext) {
+ return null;
+ }
+ return loadContext.topFrameElement;
+ };
+
+ // __collectBrowserCredentials()__.
+ // Starts observing http channels. Each channel's proxyInfo
+ // username and password is recorded for the channel's browser.
+ let collectBrowserCredentials = function() {
+ return observe("http-on-modify-request", chan => {
+ try {
+ let proxyInfo = chan.QueryInterface(Ci.nsIProxiedChannel).proxyInfo;
+ let browser = browserForChannel(chan);
+ if (browser && proxyInfo) {
+ if (!browserToCredentialsMap.has(browser)) {
+ browserToCredentialsMap.set(browser, new Map());
+ }
+ let domainMap = browserToCredentialsMap.get(browser);
+ domainMap.set(proxyInfo.username, [
+ proxyInfo.username,
+ proxyInfo.password,
+ ]);
+ }
+ } catch (e) {
+ logger.eclog(
+ 3,
+ `Error collecting browser credentials: ${e.message}, ${chan.URI.spec}`
+ );
+ }
+ });
+ };
+
+ // ## User interface
+
+ // __uiString__.
+ // Read the localized strings for this UI.
+ let uiString = function(shortName) {
+ return torbutton_get_property_string(
+ "torbutton.circuit_display." + shortName
+ );
+ };
+
+ // __localizedCountryNameFromCode(countryCode)__.
+ // Convert a country code to a localized country name.
+ // Example: `'de'` -> `'Deutschland'` in German locale.
+ let localizedCountryNameFromCode = function(countryCode) {
+ if (!countryCode) {
+ return uiString("unknown_country");
+ }
+ try {
+ return Services.intl.getRegionDisplayNames(undefined, [countryCode])[0];
+ } catch (e) {
+ return countryCode.toUpperCase();
+ }
+ };
+
+ // __showCircuitDisplay(show)__.
+ // If show === true, makes the circuit display visible.
+ let showCircuitDisplay = function(show) {
+ document.getElementById("circuit-display-container").hidden = !show;
+ };
+
+ // __xmlTree(ns, data)__.
+ // Takes an xml namespace, ns, and a
+ // data structure representing xml elements like
+ // [tag, { attr-key: attr-value }, ...xml-children]
+ // and returns nested xml element objects.
+ let xmlTree = function xmlTree(ns, data) {
+ let [type, attrs, ...children] = data;
+ let element = type.startsWith("html:")
+ ? document.createXULElement(type)
+ : document.createElementNS(ns, type);
+ for (let [key, val] of Object.entries(attrs)) {
+ element.setAttribute(key, val);
+ }
+ for (let child of children) {
+ if (child !== null && child !== undefined) {
+ element.append(typeof child === "string" ? child : xmlTree(ns, child));
+ }
+ }
+ return element;
+ };
+
+ // __htmlTree(data)__.
+ // Takes a data structure representing html elements like
+ // [tag, { attr-key: attr-value }, ...html-children]
+ // and returns nested html element objects.
+ let htmlTree = data => xmlTree("http://www.w3.org/1999/xhtml", data);
+
+ // __appendHtml(parent, data)__.
+ // Takes a data structure representing html elements like
+ // [tag, { attr-key: attr-value }, ...html-children]
+ // and appends nested html element objects to the parent element.
+ let appendHtml = (parent, data) => parent.appendChild(htmlTree(data));
+
+ // __circuitCircuitData()__.
+ // Obtains the circuit used by the given browser.
+ let currentCircuitData = function(browser) {
+ if (browser) {
+ let firstPartyDomain = getDomainForBrowser(browser);
+ let domain = firstPartyDomain || "--unknown--";
+ let domainMap = browserToCredentialsMap.get(browser);
+ let credentials = domainMap && domainMap.get(domain);
+ if (credentials) {
+ let [SOCKS_username, SOCKS_password] = credentials;
+ let nodeData = credentialsToNodeDataMap.get(
+ `${SOCKS_username}|${SOCKS_password}`
+ );
+ let domain = SOCKS_username;
+ if (browser.documentURI.host.endsWith(".tor.onion")) {
+ const service = Cc[
+ "@torproject.org/onion-alias-service;1"
+ ].getService(Ci.IOnionAliasService);
+ domain = service.getOnionAlias(browser.documentURI.host);
+ }
+ return { domain, nodeData };
+ }
+ }
+ return { domain: null, nodeData: null };
+ };
+
+ // __updateCircuitDisplay()__.
+ // Updates the Tor circuit display, showing the current domain
+ // and the relay nodes for that domain.
+ let updateCircuitDisplay = function() {
+ let { domain, nodeData } = currentCircuitData(gBrowser.selectedBrowser);
+ if (domain && nodeData) {
+ // Update the displayed information for the relay nodes.
+ let nodeHtmlList = document.getElementById("circuit-display-nodes");
+ let li = (...data) => appendHtml(nodeHtmlList, ["li", {}, ...data]);
+ nodeHtmlList.innerHTML = "";
+ li(uiString("this_browser"));
+ for (let i = 0; i < nodeData.length; ++i) {
+ let relayText;
+ if (nodeData[i].type === "bridge") {
+ relayText = uiString("tor_bridge");
+ let bridgeType = nodeData[i].bridgeType;
+ if (bridgeType === "meek_lite") {
+ relayText += ": meek";
+ } else if (bridgeType !== "vanilla" && bridgeType !== "") {
+ relayText += ": " + bridgeType;
+ }
+ } else if (nodeData[i].type == "default") {
+ relayText = localizedCountryNameFromCode(nodeData[i].countryCode);
+ }
+ const ipAddrs = nodeData[i].ipAddrs.join(", ");
+ li(
+ relayText,
+ " ",
+ ["span", { class: "circuit-ip-address" }, ipAddrs],
+ " ",
+ i === 0 && nodeData[0].type !== "bridge"
+ ? ["span", { class: "circuit-guard-info" }, uiString("guard")]
+ : null
+ );
+ }
+
+ let domainParts = [];
+ if (domain.endsWith(".onion")) {
+ for (let i = 0; i < 3; ++i) {
+ li(uiString("relay"));
+ }
+ if (domain.length > 22) {
+ domainParts.push(domain.slice(0, 7), "…", domain.slice(-12));
+ } else {
+ domainParts.push(domain);
+ }
+ } else {
+ domainParts.push(domain);
+ }
+
+ // We use a XUL html:span element so that the tooltiptext is displayed.
+ li([
+ "html:span",
+ {
+ class: "circuit-onion",
+ onclick: `
+ this.classList.add("circuit-onion-copied");
+ Cc[
+ "@mozilla.org/widget/clipboardhelper;1"
+ ].getService(Ci.nsIClipboardHelper).copyString(this.getAttribute("data-onion"))
+ `,
+ "data-onion": domain,
+ "data-text-clicktocopy": torbutton_get_property_string(
+ "torbutton.circuit_display.click_to_copy"
+ ),
+ "data-text-copied": torbutton_get_property_string(
+ "torbutton.circuit_display.copied"
+ ),
+ tooltiptext: domain,
+ },
+ ...domainParts,
+ ]);
+
+ // Hide the note about guards if we are using a bridge.
+ document.getElementById("circuit-guard-note-container").hidden =
+ nodeData[0].type === "bridge";
+ } else {
+ // Only show the Tor circuit if we have credentials and node data.
+ logger.eclog(4, "no SOCKS credentials found for current document.");
+ }
+ showCircuitDisplay(domain && nodeData);
+ };
+
+ // __syncDisplayWithSelectedTab(syncOn)__.
+ // Whenever the user starts to open the popup menu, make sure the display
+ // is the correct one for this tab. It's also possible that a new site
+ // can be loaded while the popup menu is open.
+ // Update the display if this happens.
+ let syncDisplayWithSelectedTab = (function() {
+ let listener = {
+ onLocationChange(aBrowser) {
+ if (aBrowser === gBrowser.selectedBrowser) {
+ updateCircuitDisplay();
+ }
+ },
+ };
+ return function(syncOn) {
+ let popupMenu = document.getElementById("identity-popup");
+ if (syncOn) {
+ // Update the circuit display just before the popup menu is shown.
+ popupMenu.addEventListener("popupshowing", updateCircuitDisplay);
+ // If the currently selected tab has been sent to a new location,
+ // update the circuit to reflect that.
+ gBrowser.addTabsProgressListener(listener);
+ } else {
+ // Stop syncing.
+ gBrowser.removeTabsProgressListener(listener);
+ popupMenu.removeEventListener("popupshowing", updateCircuitDisplay);
+ // Hide the display.
+ showCircuitDisplay(false);
+ }
+ };
+ })();
+
+ // __setupGuardNote()__.
+ // Call once to show the Guard note as intended.
+ let setupGuardNote = function() {
+ let guardNote = document.getElementById("circuit-guard-note-container");
+ let guardNoteString = uiString("guard_note");
+ let learnMoreString = uiString("learn_more");
+ let [noteBefore, name, noteAfter] = guardNoteString.split(/[\[\]]/);
+ let localeCode = getLocale();
+ appendHtml(guardNote, [
+ "div",
+ {},
+ noteBefore,
+ ["span", { class: "circuit-guard-name" }, name],
+ noteAfter,
+ " ",
+ [
+ "span",
+ {
+ onclick: `gBrowser.selectedTab = gBrowser.addWebTab('https://support.torproject.org/${localeCode}/tbb/tbb-2/');`,
+ class: "circuit-link",
+ },
+ learnMoreString,
+ ],
+ ]);
+ };
+
+ // __ensureCorrectPopupDimensions()__.
+ // Make sure the identity popup always displays with the correct height.
+ let ensureCorrectPopupDimensions = function() {
+ // FIXME: This is hacking with the internals of the panel view, which also
+ // sets the width and height for transitions (see PanelMultiView.jsm), so
+ // this fix is fragile.
+ // Ideally the panel view would start using a non-XUL CSS layout, which
+ // would work regardless of the content.
+ let popupMenu = document.getElementById("identity-popup");
+ let setDimensions = event => {
+ if (event.target !== popupMenu) {
+ return;
+ }
+ setTimeout(() => {
+ let view = document.querySelector(
+ "#identity-popup-multiView .panel-viewcontainer"
+ );
+ let stack = document.querySelector(
+ "#identity-popup-multiView .panel-viewstack"
+ );
+ let view2 = document.getElementById("identity-popup-mainView");
+ if (view && stack && view2) {
+ let newWidth = Math.max(
+ ...[...view2.children].map(el => el.clientWidth)
+ );
+ let newHeight = stack.clientHeight;
+ stack.setAttribute("width", newWidth);
+ view2.style.minWidth = view2.style.maxWidth = newWidth + "px";
+ view.setAttribute("width", newWidth);
+ view.setAttribute("height", newHeight);
+ }
+ });
+ };
+ let removeDimensions = event => {
+ if (event.target !== popupMenu) {
+ return;
+ }
+ let view = document.querySelector(
+ "#identity-popup-multiView .panel-viewcontainer"
+ );
+ let stack = document.querySelector(
+ "#identity-popup-multiView .panel-viewstack"
+ );
+ let view2 = document.getElementById("identity-popup-mainView");
+ if (view && stack && view2) {
+ view.removeAttribute("width");
+ view.removeAttribute("height");
+ stack.removeAttribute("width");
+ view2.style.minWidth = view2.style.maxWidth = "";
+ }
+ };
+ popupMenu.addEventListener("popupshowing", setDimensions);
+ popupMenu.addEventListener("popuphiding", removeDimensions);
+ return () => {
+ popupMenu.removeEventListener("popupshowing", setDimensions);
+ popupMenu.removeEventListener("popuphiding", removeDimensions);
+ };
+ };
+
+ // ## Main function
+
+ // __setupDisplay(enablePrefName)__.
+ // Once called, the Tor circuit display will be started whenever
+ // the "enablePref" is set to true, and stopped when it is set to false.
+ // A reference to this function (called createTorCircuitDisplay) is exported as a global.
+ let setupDisplay = function(enablePrefName) {
+ // From 79 on the identity popup is initialized lazily
+ if (gIdentityHandler._initializePopup) {
+ gIdentityHandler._initializePopup();
+ }
+ setupGuardNote();
+ let myController = null,
+ stopCollectingIsolationData = null,
+ stopCollectingBrowserCredentials = null,
+ stopEnsuringCorrectPopupDimensions = null,
+ stop = function() {
+ syncDisplayWithSelectedTab(false);
+ if (myController) {
+ if (stopCollectingIsolationData) {
+ stopCollectingIsolationData();
+ }
+ if (stopCollectingBrowserCredentials) {
+ stopCollectingBrowserCredentials();
+ }
+ if (stopEnsuringCorrectPopupDimensions) {
+ stopEnsuringCorrectPopupDimensions();
+ }
+ myController = null;
+ }
+ },
+ start = async function() {
+ if (!myController) {
+ try {
+ myController = await wait_for_controller();
+ syncDisplayWithSelectedTab(true);
+ stopCollectingIsolationData = collectIsolationData(
+ myController,
+ updateCircuitDisplay
+ );
+ stopCollectingBrowserCredentials = collectBrowserCredentials();
+ stopEnsuringCorrectPopupDimensions = ensureCorrectPopupDimensions();
+ } catch (err) {
+ logger.eclog(5, err);
+ logger.eclog(
+ 5,
+ "Disabling tor display circuit because of an error."
+ );
+ myController.close();
+ stop();
+ }
+ }
+ };
+ try {
+ let unbindPref = bindPrefAndInit(enablePrefName, on => {
+ if (on) {
+ start();
+ } else {
+ stop();
+ }
+ });
+ // When this chrome window is unloaded, we need to unbind the pref.
+ window.addEventListener("unload", function() {
+ unbindPref();
+ stop();
+ });
+ } catch (e) {
+ logger.eclog(5, "Error: " + e.message + "\n" + e.stack);
+ }
+ };
+
+ return setupDisplay;
+
+ // Finish createTorCircuitDisplay()
+})();
diff --git a/toolkit/torbutton/chrome/content/torbutton.js b/toolkit/torbutton/chrome/content/torbutton.js
new file mode 100644
index 000000000000..0959efc4fd31
--- /dev/null
+++ b/toolkit/torbutton/chrome/content/torbutton.js
@@ -0,0 +1,1044 @@
+// window globals
+var torbutton_init;
+var torbutton_new_circuit;
+
+(() => {
+ // Bug 1506 P1-P5: This is the main Torbutton overlay file. Much needs to be
+ // preserved here, but in an ideal world, most of this code should perhaps be
+ // moved into an XPCOM service, and much can also be tossed. See also
+ // individual 1506 comments for details.
+
+ // TODO: check for leaks: http://www.mozilla.org/scriptable/avoiding-leaks.html
+ // TODO: Double-check there are no strange exploits to defeat:
+ // http://kb.mozillazine.org/Links_to_local_pages_don%27t_work
+
+ /* global gBrowser, CustomizableUI,
+ createTorCircuitDisplay, gFindBarInitialized,
+ gFindBar, OpenBrowserWindow, PrivateBrowsingUtils,
+ Services, AppConstants
+ */
+
+ let {
+ unescapeTorString,
+ bindPrefAndInit,
+ getDomainForBrowser,
+ torbutton_log,
+ torbutton_get_property_string,
+ } = ChromeUtils.import("resource://torbutton/modules/utils.js");
+ let { configureControlPortModule, wait_for_controller } = ChromeUtils.import(
+ "resource://torbutton/modules/tor-control-port.js"
+ );
+
+ const { TorProtocolService } = ChromeUtils.import(
+ "resource://gre/modules/TorProtocolService.jsm"
+ );
+
+ const k_tb_tor_check_failed_topic = "Torbutton:TorCheckFailed";
+
+ var m_tb_prefs = Services.prefs;
+
+ // status
+ var m_tb_wasinited = false;
+ var m_tb_is_main_window = false;
+
+ var m_tb_control_ipc_file = null; // Set if using IPC (UNIX domain socket).
+ var m_tb_control_port = null; // Set if using TCP.
+ var m_tb_control_host = null; // Set if using TCP.
+ var m_tb_control_pass = null;
+
+ // Bug 1506 P2: This object keeps Firefox prefs in sync with Torbutton prefs.
+ // It probably could stand some simplification (See #3100). It also belongs
+ // in a component, not the XUL overlay.
+ var torbutton_unique_pref_observer = {
+ register() {
+ this.forced_ua = false;
+ m_tb_prefs.addObserver("extensions.torbutton", this);
+ m_tb_prefs.addObserver("browser.privatebrowsing.autostart", this);
+ m_tb_prefs.addObserver("javascript", this);
+ m_tb_prefs.addObserver("privacy.resistFingerprinting", this);
+ m_tb_prefs.addObserver("privacy.resistFingerprinting.letterboxing", this);
+ },
+
+ unregister() {
+ m_tb_prefs.removeObserver("extensions.torbutton", this);
+ m_tb_prefs.removeObserver("browser.privatebrowsing.autostart", this);
+ m_tb_prefs.removeObserver("javascript", this);
+ m_tb_prefs.removeObserver("privacy.resistFingerprinting", this);
+ m_tb_prefs.removeObserver(
+ "privacy.resistFingerprinting.letterboxing",
+ this
+ );
+ },
+
+ // topic: what event occurred
+ // subject: what nsIPrefBranch we're observing
+ // data: which pref has been changed (relative to subject)
+ observe(subject, topic, data) {
+ if (topic !== "nsPref:changed") {
+ return;
+ }
+ switch (data) {
+ case "browser.privatebrowsing.autostart":
+ torbutton_update_disk_prefs();
+ break;
+ case "extensions.torbutton.use_nontor_proxy":
+ torbutton_use_nontor_proxy();
+ break;
+ case "privacy.resistFingerprinting":
+ case "privacy.resistFingerprinting.letterboxing":
+ torbutton_update_fingerprinting_prefs();
+ break;
+ }
+ },
+ };
+
+ var torbutton_tor_check_observer = {
+ register() {
+ this._obsSvc = Services.obs;
+ this._obsSvc.addObserver(this, k_tb_tor_check_failed_topic);
+ },
+
+ unregister() {
+ if (this._obsSvc) {
+ this._obsSvc.removeObserver(this, k_tb_tor_check_failed_topic);
+ }
+ },
+
+ observe(subject, topic, data) {
+ if (topic === k_tb_tor_check_failed_topic) {
+ // Update all open about:tor pages.
+ torbutton_abouttor_message_handler.updateAllOpenPages();
+
+ // If the user does not have an about:tor tab open in the front most
+ // window, open one.
+ var wm = Services.wm;
+ var win = wm.getMostRecentWindow("navigator:browser");
+ if (win == window) {
+ let foundTab = false;
+ let tabBrowser = top.gBrowser;
+ for (let i = 0; !foundTab && i < tabBrowser.browsers.length; ++i) {
+ let b = tabBrowser.getBrowserAtIndex(i);
+ foundTab = b.currentURI.spec.toLowerCase() == "about:tor";
+ }
+
+ if (!foundTab) {
+ gBrowser.selectedTab = gBrowser.addTrustedTab("about:tor");
+ }
+ }
+ }
+ },
+ };
+
+ var torbutton_new_identity_observers = {
+ register() {
+ Services.obs.addObserver(this, "new-identity-requested");
+ },
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic !== "new-identity-requested") {
+ return;
+ }
+
+ // Clear the domain isolation state.
+ torbutton_log(3, "Clearing domain isolator");
+ const domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ domainIsolator.clearIsolation();
+
+ torbutton_log(3, "New Identity: Sending NEWNYM");
+ // We only support TBB for newnym.
+ if (
+ !m_tb_control_pass ||
+ (!m_tb_control_ipc_file && !m_tb_control_port)
+ ) {
+ const warning = torbutton_get_property_string(
+ "torbutton.popup.no_newnym"
+ );
+ torbutton_log(
+ 5,
+ "Torbutton cannot safely newnym. It does not have access to the Tor Control Port."
+ );
+ window.alert(warning);
+ } else {
+ const warning = torbutton_get_property_string(
+ "torbutton.popup.no_newnym"
+ );
+ torbutton_send_ctrl_cmd("SIGNAL NEWNYM")
+ .then(res => {
+ if (!res) {
+ torbutton_log(
+ 5,
+ "Torbutton was unable to request a new circuit from Tor"
+ );
+ window.alert(warning);
+ }
+ })
+ .catch(e => {
+ torbutton_log(
+ 5,
+ "Torbutton was unable to request a new circuit from Tor " + e
+ );
+ window.alert(warning);
+ });
+ }
+ },
+ };
+
+ function torbutton_is_mobile() {
+ return Services.appinfo.OS === "Android";
+ }
+
+ // Bug 1506 P2-P4: This code sets some version variables that are irrelevant.
+ // It does read out some important environment variables, though. It is
+ // called once per browser window.. This might belong in a component.
+ torbutton_init = function() {
+ torbutton_log(3, "called init()");
+
+ if (m_tb_wasinited) {
+ return;
+ }
+ m_tb_wasinited = true;
+
+ // Bug 1506 P4: These vars are very important for New Identity
+ var environ = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+
+ if (environ.exists("TOR_CONTROL_PASSWD")) {
+ m_tb_control_pass = environ.get("TOR_CONTROL_PASSWD");
+ } else if (environ.exists("TOR_CONTROL_COOKIE_AUTH_FILE")) {
+ var cookie_path = environ.get("TOR_CONTROL_COOKIE_AUTH_FILE");
+ try {
+ if ("" != cookie_path) {
+ m_tb_control_pass = torbutton_read_authentication_cookie(cookie_path);
+ }
+ } catch (e) {
+ torbutton_log(4, "unable to read authentication cookie");
+ }
+ } else {
+ try {
+ // Try to get password from Tor Launcher.
+ m_tb_control_pass = TorProtocolService.torGetPassword(false);
+ } catch (e) {}
+ }
+
+ // Try to get the control port IPC file (an nsIFile) from Tor Launcher,
+ // since Tor Launcher knows how to handle its own preferences and how to
+ // resolve relative paths.
+ try {
+ m_tb_control_ipc_file = TorProtocolService.torGetControlIPCFile();
+ } catch (e) {}
+
+ if (!m_tb_control_ipc_file) {
+ if (environ.exists("TOR_CONTROL_PORT")) {
+ m_tb_control_port = environ.get("TOR_CONTROL_PORT");
+ } else {
+ try {
+ const kTLControlPortPref = "extensions.torlauncher.control_port";
+ m_tb_control_port = m_tb_prefs.getIntPref(kTLControlPortPref);
+ } catch (e) {
+ // Since we want to disable some features when Tor Launcher is
+ // not installed (e.g., New Identity), we do not set a default
+ // port value here.
+ }
+ }
+
+ if (environ.exists("TOR_CONTROL_HOST")) {
+ m_tb_control_host = environ.get("TOR_CONTROL_HOST");
+ } else {
+ try {
+ const kTLControlHostPref = "extensions.torlauncher.control_host";
+ m_tb_control_host = m_tb_prefs.getCharPref(kTLControlHostPref);
+ } catch (e) {
+ m_tb_control_host = "127.0.0.1";
+ }
+ }
+ }
+
+ configureControlPortModule(
+ m_tb_control_ipc_file,
+ m_tb_control_host,
+ m_tb_control_port,
+ m_tb_control_pass
+ );
+
+ // Add about:tor IPC message listener.
+ window.messageManager.addMessageListener(
+ "AboutTor:Loaded",
+ torbutton_abouttor_message_handler
+ );
+
+ setupPreferencesForMobile();
+
+ torbutton_log(1, "registering Tor check observer");
+ torbutton_tor_check_observer.register();
+
+ // Create the circuit display even though the control port might not be
+ // ready yet, as the circuit display will wait for the controller to be
+ // available anyway.
+ try {
+ createTorCircuitDisplay("extensions.torbutton.display_circuit");
+ circuitDisplayCreated = true;
+ } catch (e) {
+ torbutton_log(4, "Error creating the tor circuit display " + e);
+ }
+
+ // Arrange for our about:tor content script to be loaded in each frame.
+ window.messageManager.loadFrameScript(
+ "chrome://torbutton/content/aboutTor/aboutTor-content.js",
+ true
+ );
+
+ torbutton_new_identity_observers.register();
+
+ torbutton_log(3, "init completed");
+ };
+
+ var torbutton_abouttor_message_handler = {
+ // Receive IPC messages from the about:tor content script.
+ async receiveMessage(aMessage) {
+ switch (aMessage.name) {
+ case "AboutTor:Loaded":
+ aMessage.target.messageManager.sendAsyncMessage(
+ "AboutTor:ChromeData",
+ await this.getChromeData(true)
+ );
+ break;
+ }
+ },
+
+ // Send privileged data to all of the about:tor content scripts.
+ async updateAllOpenPages() {
+ window.messageManager.broadcastAsyncMessage(
+ "AboutTor:ChromeData",
+ await this.getChromeData(false)
+ );
+ },
+
+ // The chrome data contains all of the data needed by the about:tor
+ // content process that is only available here (in the chrome process).
+ // It is sent to the content process when an about:tor window is opened
+ // and in response to events such as the browser noticing that Tor is
+ // not working.
+ async getChromeData(aIsRespondingToPageLoad) {
+ let dataObj = {
+ mobile: torbutton_is_mobile(),
+ updateChannel: AppConstants.MOZ_UPDATE_CHANNEL,
+ torOn: await torbutton_tor_check_ok(),
+ };
+
+ if (aIsRespondingToPageLoad) {
+ const kShouldNotifyPref = "torbrowser.post_update.shouldNotify";
+ if (m_tb_prefs.getBoolPref(kShouldNotifyPref, false)) {
+ m_tb_prefs.clearUserPref(kShouldNotifyPref);
+ dataObj.hasBeenUpdated = true;
+ dataObj.updateMoreInfoURL = this.getUpdateMoreInfoURL();
+ }
+ }
+
+ return dataObj;
+ },
+
+ getUpdateMoreInfoURL() {
+ try {
+ return Services.prefs.getCharPref("torbrowser.post_update.url");
+ } catch (e) {}
+
+ // Use the default URL as a fallback.
+ return Services.urlFormatter.formatURLPref(
+ "startup.homepage_override_url"
+ );
+ },
+ };
+
+ // Bug 1506 P4: Control port interaction. Needed for New Identity.
+ function torbutton_read_authentication_cookie(path) {
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(path);
+ var fileStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, 1, 0, false);
+ var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ binaryStream.setInputStream(fileStream);
+ var array = binaryStream.readByteArray(fileStream.available());
+ binaryStream.close();
+ fileStream.close();
+ return torbutton_array_to_hexdigits(array);
+ }
+
+ // Bug 1506 P4: Control port interaction. Needed for New Identity.
+ function torbutton_array_to_hexdigits(array) {
+ return array
+ .map(function(c) {
+ return String("0" + c.toString(16)).slice(-2);
+ })
+ .join("");
+ }
+
+ // Bug 1506 P4: Control port interaction. Needed for New Identity.
+ //
+ // Asynchronously executes a command on the control port.
+ // returns the response as a string, or null on error
+ async function torbutton_send_ctrl_cmd(command) {
+ const getErrorMessage = e => (e && (e.torMessage || e.message)) || "";
+ let response = null;
+ try {
+ const avoidCache = true;
+ let torController = await wait_for_controller(avoidCache);
+
+ let bytes = await torController.sendCommand(command);
+ if (!bytes.startsWith("250")) {
+ throw new Error(
+ `Unexpected command response on control port '${bytes}'`
+ );
+ }
+ response = bytes.slice(4);
+
+ torController.close();
+ } catch (err) {
+ let msg = getErrorMessage(err);
+ torbutton_log(4, `Error: ${msg}`);
+ }
+ return response;
+ }
+
+ // Bug 1506 P4: Needed for New IP Address
+ torbutton_new_circuit = function() {
+ let firstPartyDomain = getDomainForBrowser(gBrowser.selectedBrowser);
+
+ let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+
+ domainIsolator.newCircuitForDomain(firstPartyDomain);
+
+ gBrowser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ };
+
+ /* Called when we switch the use_nontor_proxy pref in either direction.
+ *
+ * Enables/disables domain isolation and then does new identity
+ */
+ function torbutton_use_nontor_proxy() {
+ let domainIsolator = Cc["@torproject.org/domain-isolator;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+
+ if (m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
+ // Disable domain isolation
+ domainIsolator.disableIsolation();
+ } else {
+ domainIsolator.enableIsolation();
+ }
+ }
+
+ async function torbutton_do_tor_check() {
+ let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ if (
+ m_tb_prefs.getBoolPref("extensions.torbutton.use_nontor_proxy") ||
+ !m_tb_prefs.getBoolPref("extensions.torbutton.test_enabled")
+ ) {
+ return;
+ } // Only do the check once.
+
+ // If we have a tor control port and transparent torification is off,
+ // perform a check via the control port.
+ const kEnvSkipControlPortTest = "TOR_SKIP_CONTROLPORTTEST";
+ const kEnvUseTransparentProxy = "TOR_TRANSPROXY";
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ if (
+ (m_tb_control_ipc_file || m_tb_control_port) &&
+ !env.exists(kEnvUseTransparentProxy) &&
+ !env.exists(kEnvSkipControlPortTest) &&
+ m_tb_prefs.getBoolPref("extensions.torbutton.local_tor_check")
+ ) {
+ if (await torbutton_local_tor_check()) {
+ checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful;
+ } else {
+ // The check failed. Update toolbar icon and tooltip.
+ checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
+ }
+ } else {
+ // A local check is not possible, so perform a remote check.
+ torbutton_initiate_remote_tor_check();
+ }
+ }
+
+ async function torbutton_local_tor_check() {
+ let didLogError = false;
+
+ let proxyType = m_tb_prefs.getIntPref("network.proxy.type");
+ if (0 == proxyType) {
+ return false;
+ }
+
+ // Ask tor for its SOCKS listener address and port and compare to the
+ // browser preferences.
+ const kCmdArg = "net/listeners/socks";
+ let resp = await torbutton_send_ctrl_cmd("GETINFO " + kCmdArg);
+ if (!resp) {
+ return false;
+ }
+
+ function logUnexpectedResponse() {
+ if (!didLogError) {
+ didLogError = true;
+ torbutton_log(
+ 5,
+ "Local Tor check: unexpected GETINFO response: " + resp
+ );
+ }
+ }
+
+ function removeBrackets(aStr) {
+ // Remove enclosing square brackets if present.
+ if (aStr.startsWith("[") && aStr.endsWith("]")) {
+ return aStr.substr(1, aStr.length - 2);
+ }
+
+ return aStr;
+ }
+
+ // Sample response: net/listeners/socks="127.0.0.1:9149" "127.0.0.1:9150"
+ // First, check for and remove the command argument prefix.
+ if (0 != resp.indexOf(kCmdArg + "=")) {
+ logUnexpectedResponse();
+ return false;
+ }
+ resp = resp.substr(kCmdArg.length + 1);
+
+ // Retrieve configured proxy settings and check each listener against them.
+ // When the SOCKS prefs are set to use IPC (e.g., a Unix domain socket), a
+ // file URL should be present in network.proxy.socks.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1211567
+ let socksAddr = m_tb_prefs.getCharPref("network.proxy.socks");
+ let socksPort = m_tb_prefs.getIntPref("network.proxy.socks_port");
+ let socksIPCPath;
+ if (socksAddr && socksAddr.startsWith("file:")) {
+ // Convert the file URL to a file path.
+ try {
+ let ioService = Services.io;
+ let fph = ioService
+ .getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler);
+ socksIPCPath = fph.getFileFromURLSpec(socksAddr).path;
+ } catch (e) {
+ torbutton_log(5, "Local Tor check: IPC file error: " + e);
+ return false;
+ }
+ } else {
+ socksAddr = removeBrackets(socksAddr);
+ }
+
+ // Split into quoted strings. This code is adapted from utils.splitAtSpaces()
+ // within tor-control-port.js; someday this code should use the entire
+ // tor-control-port.js framework.
+ let addrArray = [];
+ resp.replace(/((\S*?"(.*?)")+\S*|\S+)/g, function(a, captured) {
+ addrArray.push(captured);
+ });
+
+ let foundSocksListener = false;
+ for (let i = 0; !foundSocksListener && i < addrArray.length; ++i) {
+ let addr;
+ try {
+ addr = unescapeTorString(addrArray[i]);
+ } catch (e) {}
+ if (!addr) {
+ continue;
+ }
+
+ // Remove double quotes if present.
+ let len = addr.length;
+ if (len > 2 && '"' == addr.charAt(0) && '"' == addr.charAt(len - 1)) {
+ addr = addr.substring(1, len - 1);
+ }
+
+ if (addr.startsWith("unix:")) {
+ if (!socksIPCPath) {
+ continue;
+ }
+
+ // Check against the configured UNIX domain socket proxy.
+ let path = addr.substring(5);
+ torbutton_log(2, "Tor socks listener (Unix domain socket): " + path);
+ foundSocksListener = socksIPCPath === path;
+ } else if (!socksIPCPath) {
+ // Check against the configured TCP proxy. We expect addr:port where addr
+ // may be an IPv6 address; that is, it may contain colon characters.
+ // Also, we remove enclosing square brackets before comparing addresses
+ // because tor requires them but Firefox does not.
+ let idx = addr.lastIndexOf(":");
+ if (idx < 0) {
+ logUnexpectedResponse();
+ } else {
+ let torSocksAddr = removeBrackets(addr.substring(0, idx));
+ let torSocksPort = parseInt(addr.substring(idx + 1), 10);
+ if (torSocksAddr.length < 1 || isNaN(torSocksPort)) {
+ logUnexpectedResponse();
+ } else {
+ torbutton_log(
+ 2,
+ "Tor socks listener: " + torSocksAddr + ":" + torSocksPort
+ );
+ foundSocksListener =
+ socksAddr === torSocksAddr && socksPort === torSocksPort;
+ }
+ }
+ }
+ }
+
+ return foundSocksListener;
+ } // torbutton_local_tor_check
+
+ function torbutton_initiate_remote_tor_check() {
+ let obsSvc = Services.obs;
+ try {
+ let checkSvc = Cc[
+ "@torproject.org/torbutton-torCheckService;1"
+ ].getService(Ci.nsISupports).wrappedJSObject;
+ let req = checkSvc.createCheckRequest(true); // async
+ req.onreadystatechange = function(aEvent) {
+ if (req.readyState === 4) {
+ let ret = checkSvc.parseCheckResponse(req);
+
+ // If we received an error response from check.torproject.org,
+ // set the status of the tor check to failure (we don't want
+ // to indicate failure if we didn't receive a response).
+ if (
+ ret == 2 ||
+ ret == 3 ||
+ ret == 5 ||
+ ret == 6 ||
+ ret == 7 ||
+ ret == 8
+ ) {
+ checkSvc.statusOfTorCheck = checkSvc.kCheckFailed;
+ obsSvc.notifyObservers(null, k_tb_tor_check_failed_topic);
+ } else if (ret == 4) {
+ checkSvc.statusOfTorCheck = checkSvc.kCheckSuccessful;
+ } // Otherwise, redo the check later
+
+ torbutton_log(3, "Tor remote check done. Result: " + ret);
+ }
+ };
+
+ torbutton_log(3, "Sending async Tor remote check");
+ req.send(null);
+ } catch (e) {
+ if (e.result == 0x80004005) {
+ // NS_ERROR_FAILURE
+ torbutton_log(5, "Tor check failed! Is tor running?");
+ } else {
+ torbutton_log(5, "Tor check failed! Tor internal error: " + e);
+ }
+
+ obsSvc.notifyObservers(null, k_tb_tor_check_failed_topic);
+ }
+ } // torbutton_initiate_remote_tor_check()
+
+ async function torbutton_tor_check_ok() {
+ await torbutton_do_tor_check();
+ let checkSvc = Cc["@torproject.org/torbutton-torCheckService;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ return checkSvc.kCheckFailed != checkSvc.statusOfTorCheck;
+ }
+
+ function torbutton_update_disk_prefs() {
+ var mode = m_tb_prefs.getBoolPref("browser.privatebrowsing.autostart");
+
+ m_tb_prefs.setBoolPref("browser.cache.disk.enable", !mode);
+ m_tb_prefs.setBoolPref("places.history.enabled", !mode);
+
+ m_tb_prefs.setBoolPref("security.nocertdb", mode);
+
+ // No way to clear this beast during New Identity. Leave it off.
+ //m_tb_prefs.setBoolPref("dom.indexedDB.enabled", !mode);
+
+ m_tb_prefs.setBoolPref("permissions.memory_only", mode);
+
+ // Third party abuse. Leave it off for now.
+ //m_tb_prefs.setBoolPref("browser.cache.offline.enable", !mode);
+
+ // Force prefs to be synced to disk
+ Services.prefs.savePrefFile(null);
+ }
+
+ function torbutton_update_fingerprinting_prefs() {
+ var mode = m_tb_prefs.getBoolPref("privacy.resistFingerprinting");
+ var letterboxing = m_tb_prefs.getBoolPref(
+ "privacy.resistFingerprinting.letterboxing",
+ false
+ );
+ m_tb_prefs.setBoolPref(
+ "extensions.torbutton.resize_new_windows",
+ mode && !letterboxing
+ );
+
+ // Force prefs to be synced to disk
+ Services.prefs.savePrefFile(null);
+ }
+
+ // Bug 1506 P1: This function just cleans up prefs that got set badly in previous releases
+ function torbutton_fixup_old_prefs() {
+ if (m_tb_prefs.getIntPref("extensions.torbutton.pref_fixup_version") < 1) {
+ // TBB 5.0a3 had bad Firefox code that silently flipped this pref on us
+ if (m_tb_prefs.prefHasUserValue("browser.newtabpage.enhanced")) {
+ m_tb_prefs.clearUserPref("browser.newtabpage.enhanced");
+ // TBB 5.0a3 users had all the necessary data cached in
+ // directoryLinks.json. This meant that resetting the pref above
+ // alone was not sufficient as the tiles features uses the cache
+ // even if the pref indicates that feature should be disabled.
+ // We flip the preference below as this forces a refetching which
+ // effectively results in an empty JSON file due to our spoofed
+ // URLs.
+ let matchOS = m_tb_prefs.getBoolPref("intl.locale.matchOS");
+ m_tb_prefs.setBoolPref("intl.locale.matchOS", !matchOS);
+ m_tb_prefs.setBoolPref("intl.locale.matchOS", matchOS);
+ }
+
+ // For some reason, the Share This Page button also survived the
+ // TBB 5.0a4 update's attempt to remove it.
+ if (m_tb_prefs.prefHasUserValue("browser.uiCustomization.state")) {
+ m_tb_prefs.clearUserPref("browser.uiCustomization.state");
+ }
+
+ m_tb_prefs.setIntPref("extensions.torbutton.pref_fixup_version", 1);
+ }
+ }
+
+ // ---------------------- Event handlers -----------------
+
+ // Bug 1506 P1-P3: Most of these observers aren't very important.
+ // See their comments for details
+ function torbutton_do_main_window_startup() {
+ torbutton_log(3, "Torbutton main window startup");
+ m_tb_is_main_window = true;
+ torbutton_unique_pref_observer.register();
+ }
+
+ // Bug 1506 P4: Most of this function is now useless, save
+ // for the very important SOCKS environment vars at the end.
+ // Those could probably be rolled into a function with the
+ // control port vars, though. See 1506 comments inside.
+ function torbutton_do_startup() {
+ if (m_tb_prefs.getBoolPref("extensions.torbutton.startup")) {
+ // Bug 1506: Should probably be moved to an XPCOM component
+ torbutton_do_main_window_startup();
+
+ // For charsets
+ torbutton_update_fingerprinting_prefs();
+
+ // Bug 30565: sync browser.privatebrowsing.autostart with security.nocertdb
+ torbutton_update_disk_prefs();
+
+ // For general pref fixups to handle pref damage in older versions
+ torbutton_fixup_old_prefs();
+
+ m_tb_prefs.setBoolPref("extensions.torbutton.startup", false);
+ }
+ }
+
+ // Bug 1506 P3: Used to decide if we should resize the window.
+ //
+ // Returns true if the window wind is neither maximized, full screen,
+ // ratpoisioned/evilwmed, nor minimized.
+ function torbutton_is_windowed(wind) {
+ torbutton_log(
+ 3,
+ "Window: (" +
+ wind.outerWidth +
+ "," +
+ wind.outerHeight +
+ ") ?= (" +
+ wind.screen.availWidth +
+ "," +
+ wind.screen.availHeight +
+ ")"
+ );
+ if (
+ wind.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED ||
+ wind.windowState == Ci.nsIDOMChromeWindow.STATE_MAXIMIZED
+ ) {
+ torbutton_log(2, "Window is minimized/maximized");
+ return false;
+ }
+ if ("fullScreen" in wind && wind.fullScreen) {
+ torbutton_log(2, "Window is fullScreen");
+ return false;
+ }
+ if (
+ wind.outerHeight == wind.screen.availHeight &&
+ wind.outerWidth == wind.screen.availWidth
+ ) {
+ torbutton_log(3, "Window is ratpoisoned/evilwm'ed");
+ return false;
+ }
+
+ torbutton_log(2, "Window is normal");
+ return true;
+ }
+
+ function showSecurityPreferencesPanel(chromeWindow) {
+ const tabBrowser = chromeWindow.BrowserApp;
+ let settingsTab = null;
+
+ const SECURITY_PREFERENCES_URI =
+ "chrome://torbutton/content/preferences.xhtml";
+
+ tabBrowser.tabs.some(function(tab) {
+ // If the security prefs tab is opened, send the user to it
+ if (tab.browser.currentURI.spec === SECURITY_PREFERENCES_URI) {
+ settingsTab = tab;
+ return true;
+ }
+ return false;
+ });
+
+ if (settingsTab === null) {
+ // Open up the settings panel in a new tab.
+ tabBrowser.addTab(SECURITY_PREFERENCES_URI, {
+ selected: true,
+ parentId: tabBrowser.selectedTab.id,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ } else {
+ // Activate an existing settings panel tab.
+ tabBrowser.selectTab(settingsTab);
+ }
+ }
+
+ function setupPreferencesForMobile() {
+ if (!torbutton_is_mobile()) {
+ return;
+ }
+
+ torbutton_log(4, "Setting up settings preferences for Android.");
+
+ const chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // Add the extension's chrome menu item to the main browser menu.
+ chromeWindow.NativeWindow.menu.add({
+ name: torbutton_get_property_string(
+ "torbutton.security_settings.menu.title"
+ ),
+ callback: showSecurityPreferencesPanel.bind(this, chromeWindow),
+ });
+ }
+
+ // Bug 1506 P3: This is needed pretty much only for the window resizing.
+ // See comments for individual functions for details
+ function torbutton_new_window(event) {
+ torbutton_log(3, "New window");
+ var browser = window.gBrowser;
+
+ if (!browser) {
+ torbutton_log(5, "No browser for new window.");
+ return;
+ }
+
+ if (!m_tb_wasinited) {
+ torbutton_init();
+ }
+
+ torbutton_do_startup();
+
+ let progress = Cc["@mozilla.org/docloaderservice;1"].getService(
+ Ci.nsIWebProgress
+ );
+
+ if (torbutton_is_windowed(window)) {
+ progress.addProgressListener(
+ torbutton_resizelistener,
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
+ );
+ }
+ }
+
+ // Bug 1506 P2: This is only needed because we have observers
+ // in XUL that should be in an XPCOM component
+ function torbutton_close_window(event) {
+ torbutton_tor_check_observer.unregister();
+
+ window.removeEventListener("sizemodechange", m_tb_resize_handler);
+
+ // TODO: This is a real ghetto hack.. When the original window
+ // closes, we need to find another window to handle observing
+ // unique events... The right way to do this is to move the
+ // majority of torbutton functionality into a XPCOM component..
+ // But that is a major overhaul..
+ if (m_tb_is_main_window) {
+ torbutton_log(3, "Original window closed. Searching for another");
+ var wm = Services.wm;
+ var enumerator = wm.getEnumerator("navigator:browser");
+ while (enumerator.hasMoreElements()) {
+ var win = enumerator.getNext();
+ // For some reason, when New Identity is called from a pref
+ // observer (ex: torbutton_use_nontor_proxy) on an ASAN build,
+ // we sometimes don't have this symbol set in the new window yet.
+ // However, the new window will run this init later in that case,
+ // as it does in the OSX case.
+ if (win != window && "torbutton_do_main_window_startup" in win) {
+ torbutton_log(3, "Found another window");
+ win.torbutton_do_main_window_startup();
+ m_tb_is_main_window = false;
+ break;
+ }
+ }
+
+ torbutton_unique_pref_observer.unregister();
+
+ if (m_tb_is_main_window) {
+ // main window not reset above
+ // This happens on Mac OS because they allow firefox
+ // to still persist without a navigator window
+ torbutton_log(3, "Last window closed. None remain.");
+ m_tb_prefs.setBoolPref("extensions.torbutton.startup", true);
+ m_tb_is_main_window = false;
+ }
+ }
+ }
+
+ window.addEventListener("load", torbutton_new_window);
+ window.addEventListener("unload", torbutton_close_window);
+
+ var m_tb_resize_handler = null;
+ var m_tb_resize_date = null;
+
+ // Bug 1506 P1/P3: Setting a fixed window size is important, but
+ // probably not for android.
+ var torbutton_resizelistener = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+
+ onLocationChange(aProgress, aRequest, aURI) {},
+ onStateChange(aProgress, aRequest, aFlag, aStatus) {
+ if (aFlag & Ci.nsIWebProgressListener.STATE_STOP) {
+ m_tb_resize_handler = async function() {
+ // Wait for end of execution queue to ensure we have correct windowState.
+ await new Promise(resolve => setTimeout(resolve, 0));
+ if (
+ window.windowState === window.STATE_MAXIMIZED ||
+ window.windowState === window.STATE_FULLSCREEN
+ ) {
+ if (
+ m_tb_prefs.getBoolPref(
+ "extensions.torbutton.resize_new_windows"
+ ) &&
+ m_tb_prefs.getIntPref(
+ "extensions.torbutton.maximize_warnings_remaining"
+ ) > 0
+ ) {
+ // Do not add another notification if one is already showing.
+ const kNotificationName = "torbutton-maximize-notification";
+ let box = gBrowser.getNotificationBox();
+ if (box.getNotificationWithValue(kNotificationName)) {
+ return;
+ }
+
+ // Rate-limit showing our notification if needed.
+ if (m_tb_resize_date === null) {
+ m_tb_resize_date = Date.now();
+ } else {
+ // We wait at least another second before we show a new
+ // notification. Should be enough to rule out OSes that call our
+ // handler rapidly due to internal workings.
+ if (Date.now() - m_tb_resize_date < 1000) {
+ return;
+ }
+ // Resizing but we need to reset |m_tb_resize_date| now.
+ m_tb_resize_date = Date.now();
+ }
+
+ // No need to get "OK" translated again.
+ let sbSvc = Services.strings;
+ let bundle = sbSvc.createBundle(
+ "chrome://global/locale/commonDialogs.properties"
+ );
+ let button_label = bundle.GetStringFromName("OK");
+
+ let buttons = [
+ {
+ label: button_label,
+ accessKey: "O",
+ popup: null,
+ callback() {
+ m_tb_prefs.setIntPref(
+ "extensions.torbutton.maximize_warnings_remaining",
+ m_tb_prefs.getIntPref(
+ "extensions.torbutton.maximize_warnings_remaining"
+ ) - 1
+ );
+ },
+ },
+ ];
+
+ let priority = box.PRIORITY_WARNING_LOW;
+ let message = torbutton_get_property_string(
+ "torbutton.maximize_warning"
+ );
+
+ box.appendNotification(
+ message,
+ kNotificationName,
+ null,
+ priority,
+ buttons
+ );
+ }
+ }
+ }; // m_tb_resize_handler
+
+ // We need to handle OSes that auto-maximize windows depending on user
+ // settings and/or screen resolution in the start-up phase and users that
+ // try to shoot themselves in the foot by maximizing the window manually.
+ // We add a listener which is triggerred as soon as the window gets
+ // maximized (windowState = 1). We are resizing during start-up but not
+ // later as the user should see only a warning there as a stopgap before
+ // #14229 lands.
+ // Alas, the Firefox window code is handling the event not itself:
+ // "// Note the current implementation of SetSizeMode just stores
+ // // the new state; it doesn't actually resize. So here we store
+ // // the state and pass the event on to the OS."
+ // (See: https://mxr.mozilla.org/mozilla-esr31/source/xpfe/appshell/src/
+ // nsWebShellWindow.cpp#348)
+ // This means we have to cope with race conditions and resizing in the
+ // sizemodechange listener is likely to fail. Thus, we add a specific
+ // resize listener that is doing the work for us. It seems (at least on
+ // Ubuntu) to be the case that maximizing (and then again normalizing) of
+ // the window triggers more than one resize event the first being not the
+ // one we need. Thus we can't remove the listener after the first resize
+ // event got fired. Thus, we have the rather klunky setTimeout() call.
+ window.addEventListener("sizemodechange", m_tb_resize_handler);
+
+ let progress = Cc["@mozilla.org/docloaderservice;1"].getService(
+ Ci.nsIWebProgress
+ );
+ progress.removeProgressListener(this);
+ }
+ }, // onStateChange
+
+ onProgressChange(
+ aProgress,
+ aRequest,
+ curSelfProgress,
+ maxSelfProgress,
+ curTotalProgress,
+ maxTotalProgress
+ ) {},
+ onStatusChange(aProgress, aRequest, stat, message) {},
+ onSecurityChange() {},
+ };
+})();
+//vim:set ts=4
diff --git a/toolkit/torbutton/chrome/skin/about-wordmark.png b/toolkit/torbutton/chrome/skin/about-wordmark.png
new file mode 100644
index 000000000000..1becd875f9ac
Binary files /dev/null and b/toolkit/torbutton/chrome/skin/about-wordmark.png differ
diff --git a/toolkit/torbutton/chrome/skin/aboutDialog.css b/toolkit/torbutton/chrome/skin/aboutDialog.css
new file mode 100644
index 000000000000..981d68e22698
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/aboutDialog.css
@@ -0,0 +1,34 @@
+#leftBox {
+ background-image: url('chrome://branding/content/icon256.png');
+ background-position: right top;
+ background-size: 180px;
+}
+
+#rightBox {
+ background-image: url('chrome://torbutton/skin/about-wordmark.png');
+ background-position: left top;
+}
+
+#contributeDesc {
+ display: none;
+}
+
+#communityDesc {
+ display: none;
+}
+
+#trademark {
+ display: none;
+}
+
+#trademarkTor {
+ font-size: xx-small;
+ text-align: center;
+ color: #999999;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+#bottomBox > hbox:not(#newBottom) {
+ display: none;
+}
diff --git a/toolkit/torbutton/chrome/skin/aboutTor.css b/toolkit/torbutton/chrome/skin/aboutTor.css
new file mode 100644
index 000000000000..007c46d390b4
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/aboutTor.css
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2019, The Tor Project, Inc.
+ * See LICENSE for licensing information.
+ *
+ * vim: set sw=2 sts=2 ts=8 et syntax=css:
+ */
+
+:root {
+ --abouttor-text-color: white;
+ --abouttor-bg-toron-color: #420C5D;
+ --abouttor-bg-toroff-color: #A4000F;
+ --onion-opacity: 0.2;
+ --onion-color: #fff;
+ --onion-radius: 75px;
+}
+
+* {
+ padding: 0px;
+ margin: 0px;
+}
+
+html {
+ height: 100%;
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ width: 100%;
+ height: 100%;
+ margin: 0px auto;
+ padding: 0px 0px;
+ font-family: Helvetica, Arial, sans-serif;
+ color: var(--abouttor-text-color);
+ background-color: var(--abouttor-bg-toroff-color);
+ background-attachment: fixed;
+ background-size: 100% 100%;
+}
+
+body[toron] {
+ background-color: var(--abouttor-bg-toron-color);
+}
+
+/* Hide the entire document by default to avoid showing the incorrect
+ * Tor on / off status (that info must be retrieved from the chrome
+ * process, which involves IPC when multiprocess mode is enabled). An
+ * initialized attribute will be added as soon as the status is known.
+ */
+body:not([initialized]) {
+ display: none;
+}
+
+.torcontent-container {
+ margin: 40px 20px 28px 20px;
+ display: flex;
+ flex-direction: column;
+}
+
+ at media only screen and (min-width: 900px) {
+ .torcontent-container {
+ margin: 40px auto 28px auto;
+ max-width: 1000px;
+ }
+}
+
+#torbrowser-info {
+ position: absolute;
+ top: 16px;
+ offset-inline-end: 16px;
+ inset-inline-end: 16px;
+ height: 36px;
+ width: 200px;
+}
+
+#torbrowser-info div {
+ font-size: 14px;
+ white-space: pre-wrap;
+ text-align: end;
+ margin-bottom: 6px;
+}
+
+/* Hide "View Changelog" link if update channel is not release or alpha. */
+body:not([updatechannel="release"]):not([updatechannel="alpha"]) #torbrowser-changelog-link {
+ display: none;
+}
+
+a {
+ color: var(--abouttor-text-color);
+}
+
+#torstatus {
+ margin-top: 135px;
+ display: flex;
+ flex-direction: column;
+ align-content: center;
+ justify-content: flex-end;
+ min-height: 92px;
+}
+
+.top {
+ white-space: nowrap;
+}
+
+.torstatus-container {
+ text-align: center;
+}
+
+body[hasbeenupdated] .hideIfHasBeenUpdated,
+body[toron] .hideIfTorOn,
+body:not([toron]) .hideIfTorOff {
+ display: none;
+}
+
+body:not([hasbeenupdated]) .showIfHasBeenUpdated {
+ display: none;
+}
+
+.torstatus-container * {
+ text-align: center;
+}
+
+.top div.hideIfTorOff .heading1 {
+ margin-top: 20px;
+}
+
+.top .heading1 {
+ font-size: 50px;
+ font-weight: 300;
+ margin-bottom: 5px;
+}
+
+.top .heading2 {
+ font-size: 15px;
+ margin-bottom: 20px;
+}
+
+#bottom {
+ margin-top: 10vh;
+ margin-inline: auto;
+ max-width: 730px;
+}
+
+#bottom p {
+ font-size: 10px;
+ text-align: start;
+ padding-bottom: 20px;
+}
+
+#bottom p:last-child {
+ padding-bottom: 0px;
+}
+
+#bottom p.moreInfoLink {
+ font-size: 15px;
+}
+
+#bottom img.imageStyle {
+ padding-inline-end: 0.5em;
+ height: 1.5em;
+ vertical-align: bottom;
+ -moz-context-properties: fill;
+ fill: white;
+}
+
+/* Hide the linebreaks on large enough screens (desktops, laptops, and
+ * tablets).
+ */
+ at media only screen and (min-width: 768px) {
+ #bottom br {
+ display: none;
+ }
+}
+
+.searchbox form {
+ max-width: 575px;
+ width: auto;
+ margin-block: 0;
+ margin-inline: auto;
+ text-align: left;
+}
+
+.searchwrapper {
+ display: flex;
+ height: 46px;
+ border: 1px solid black;
+ border-radius: 8px;
+ background-color: white;
+}
+
+.searchlabel {
+ height: auto;
+ width: 50px;
+ display: inline-block;
+ background: url('dax-logo.svg') no-repeat center center;
+ background-size: 30px 30px;
+}
+
+#search-button {
+ height: auto;
+ width: 36px;
+ border: 0;
+ background: url('chrome://browser/skin/forward.svg') no-repeat center center;
+ background-size: 16px 16px;
+ cursor: pointer;
+}
+
+#search-button:dir(rtl) {
+ transform: scaleX(-1);
+}
+
+#search-button:hover {
+ background-color: rgba(12, 12, 13, 0.1); /* same as Firefox's about:home */
+}
+
+#bannerRoot {
+ width: 690px;
+ padding: 20px 25px 25px;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 6px;
+ margin: 35px auto 0px;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ position: static;
+}
+
+#bannerTextCol {
+ padding-top: 8px;
+ padding-inline-start: 15px;
+ font-size: 15px;
+ line-height: normal;
+ color: #FFFFFF;
+ width: 633px;
+}
+
+#bannerHeader {
+ font-weight: bold;
+}
+
+#bannerImg {
+ width: 32px;
+ position: static;
+}
+
+a#bannerLink {
+ text-decoration: underline;
+}
+
+a#bannerLink:hover {
+ text-decoration: none;
+}
+
+/* #search-text is the search input field */
+#search-text {
+ flex: 1;
+ border: none;
+ padding: 4px 2px 5px 2px;
+ margin: 0;
+ font-size: 15px;
+}
+
+#search-text:focus-visible {
+ outline: none;
+}
+
+#search-button:focus-visible {
+ outline: 1px dotted black;
+ outline-offset: -6px;
+}
+
+
+/*
+ * Mobile specific css
+ */
+
+.torcontent-logo {
+ display: none;
+}
+
+body[mobile] #torbrowser-changelog-link,
+body[mobile] .searchbox,
+body[mobile] .top .heading2,
+body[mobile] #manual,
+body[mobile] #mission {
+ display: none;
+}
+
+body[mobile] .top {
+ white-space: normal;
+ margin-top: 0px !important;
+}
+
+body[mobile] .torcontent-logo {
+ display: block;
+ margin-top: 20px;
+ width: 50%;
+ height: auto;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+body[mobile] .top .heading1 {
+ font-size: 62px;
+ line-height: 1.1
+}
+
+body[mobile] .onion-pattern-container {
+ flex: none;
+ z-index: -1; /* ensure that circles do not block access to clickable links */
+ position: absolute;
+ bottom: 0px;
+}
diff --git a/toolkit/torbutton/chrome/skin/banner-warning.svg b/toolkit/torbutton/chrome/skin/banner-warning.svg
new file mode 100644
index 000000000000..2f4bf1b887d8
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/banner-warning.svg
@@ -0,0 +1 @@
+<svg fill="none" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" fill="#ffa436" r="16"/><g fill="#0c0c0d"><path d="m14.2303 23.393c-3.5375-.6033-6.2303-3.6837-6.2303-7.393 0-4.1421 3.3579-7.5 7.5-7.5s7.5 3.3579 7.5 7.5c0 .669-.0876 1.3176-.252 1.9349l-.9277-1.6176c.0052-.1051.0078-.2109.0078-.3173 0-3.4949-2.8332-6.32812-6.3281-6.32812s-6.32812 2.83322-6.32812 6.32812c0 2.9851 2.06682 5.4874 4.84752 6.154-.06.4351.0216.8644.2109 1.239 [...]
\ No newline at end of file
diff --git a/toolkit/torbutton/chrome/skin/dax-logo.svg b/toolkit/torbutton/chrome/skin/dax-logo.svg
new file mode 100644
index 000000000000..94ad7c356329
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/dax-logo.svg
@@ -0,0 +1 @@
+<svg width="120" height="120" viewbox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="50.833" cy="50.833" r="50.25"/><linearGradient x1="3.084%" y1="49.368%" x2="100.592%" y2="49.368%" id="c"><stop stop-color="#6176B9" offset=".56%"/><stop stop-color="#394A9F" offset="69.1%"/></linearGradient><linearGradient x1="-.006%" y1="49.006%" x2="98.932%" y2="49.006%" id="d"><stop stop-color="#6176B9" offset=".56%"/><stop stop-co [...]
\ No newline at end of file
diff --git a/toolkit/torbutton/chrome/skin/icon-newsletter.png b/toolkit/torbutton/chrome/skin/icon-newsletter.png
new file mode 100644
index 000000000000..7532ba9c273f
Binary files /dev/null and b/toolkit/torbutton/chrome/skin/icon-newsletter.png differ
diff --git a/toolkit/torbutton/chrome/skin/preferences-mobile.css b/toolkit/torbutton/chrome/skin/preferences-mobile.css
new file mode 100644
index 000000000000..b6d54e8dff63
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/preferences-mobile.css
@@ -0,0 +1,47 @@
+.wrapper {
+ display: flex;
+}
+
+.outer-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.inner-wrapper {
+ height: 350px;
+ width: 100%;
+ max-width: 600px;
+ flex-direction: column;
+ align-items: center;
+}
+
+#torbutton_sec_slider {
+ align-self: stretch;
+ width: auto;
+ height: auto;
+}
+
+datalist {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-self: stretch;
+}
+
+p {
+ text-align: justify;
+}
+
+div > p {
+ margin-top: 0px;
+}
+
+.title {
+ font-weight: bold;
+ line-height: 30px;
+}
+
+.wrapper .inner-wrapper * {
+ margin-top: 10px;
+}
diff --git a/toolkit/torbutton/chrome/skin/preferences.css b/toolkit/torbutton/chrome/skin/preferences.css
new file mode 100644
index 000000000000..013b36771aea
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/preferences.css
@@ -0,0 +1,7 @@
+.slider-text-weight {
+ font-weight: bold;
+}
+
+.slider-text-size {
+ font-size: 95%;
+}
diff --git a/toolkit/torbutton/chrome/skin/tor-circuit-display.css b/toolkit/torbutton/chrome/skin/tor-circuit-display.css
new file mode 100644
index 000000000000..4f03b8824464
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/tor-circuit-display.css
@@ -0,0 +1,193 @@
+/*
+This CSS file is for styling the tor circuit display, which consists of a title,
+a domain, and a bulleted list.
+
+Each bullet in the circuit node list is supposed to represent a Tor circuit node,
+and lines drawn between them to represent Tor network inter-relay connections.
+*/
+
+#circuit-display-container {
+ margin-inline: 8px;
+ margin-top: 0px;
+ margin-bottom: 4px;
+ padding: 0px 3px;
+}
+
+#circuit-display-container > toolbarseparator {
+ margin-block: 0px;
+ margin-inline: -3px;
+ width: calc(100% + 6px);
+}
+
+#circuit-display-headline {
+ margin-inline-start: 8px;
+}
+
+#circuit-display-header {
+ background-image: url(chrome://torbutton/skin/torbutton.svg);
+ background-repeat: no-repeat;
+ background-position: 4px 50%;
+ background-size: 16px auto;
+ min-height: 16px;
+ -moz-context-properties: fill, fill-opacity;
+ fill: currentColor;
+ margin-block: 8px;
+}
+
+#circuit-display-header:-moz-locale-dir(rtl) {
+ background-position-x: right;
+}
+
+#circuit-display-header > hbox {
+ margin-inline-start: 16px;
+}
+
+#circuit-display-content {
+ width: 100%;
+}
+
+#circuit-display-content > * {
+ margin-inline: 0px;
+ margin-inline-start: 16px;
+ margin-top: 0px;
+ margin-bottom: 8px;
+}
+
+#circuit-display-content > hbox:not([hidden]) {
+ display: flex;
+}
+
+#circuit-reload-button-container {
+ padding-top: 8px;
+ justify-content: flex-end;
+}
+
+#circuit-reload-content {
+ cursor: default;
+ width: 100%;
+}
+
+/* Format the domain text. */
+#circuit-display-domain {
+ opacity: 0.8;
+}
+
+#circuit-div {
+ position: relative;
+ margin-inline-start: 6px;
+}
+
+/* Format the circuit node list. */
+ul#circuit-display-nodes {
+ line-height:32px;
+ padding-inline-start: 8px;
+}
+
+/* Hide default bullets, and draw our own bullets */
+ul#circuit-display-nodes li {
+ background-image: url(chrome://torbutton/skin/tor-circuit-line.svg);
+ background-position: left center;
+ background-repeat: no-repeat;
+ list-style: none;
+ /* tor circuit svg are 16px wide */
+ padding-inline-start: calc(16px + 0.5em);
+ white-space: nowrap;
+ max-height: 32px;
+ -moz-context-properties: stroke, stroke-opacity;
+ stroke: currentColor;
+ stroke-opacity: 100%;
+}
+
+ul#circuit-display-nodes li:dir(rtl) {
+ background-position: right center;
+}
+
+ul#circuit-display-nodes li:first-child {
+ background-image: url(chrome://torbutton/skin/tor-circuit-line-first.svg);
+}
+
+ul#circuit-display-nodes li:last-child {
+ background-image: url(chrome://torbutton/skin/tor-circuit-line-last.svg);
+}
+
+.circuit-ip-address {
+ font-size: 80%;
+ opacity: 0.7;
+ padding-inline-start: 3px;
+}
+
+.circuit-guard-info {
+ font-size: 80%;
+ color: var(--button-primary-bgcolor);
+ font-weight: bold;
+ padding-inline-start: 3px;
+}
+
+#circuit-reload-button {
+ background-color: var(--button-primary-bgcolor);
+ border-radius: 4px;
+ border-width: 0px;
+ color: var(--button-primary-color);
+ font-weight: 600;
+ padding: 8px 16px;
+ margin: 0;
+}
+#circuit-reload-button:hover {
+ background-color: var(--button-primary-hover-bgcolor);
+}
+
+#circuit-reload-button:active {
+ background-color: var(--button-primary-active-bgcolor);
+}
+
+#circuit-guard-note-container div {
+ margin-inline-start: 8px;
+}
+
+.circuit-guard-name {
+ font-weight: bold;
+}
+
+.circuit-link {
+ cursor: pointer;
+ color: var(--urlbar-popup-url-color);
+}
+
+.circuit-link:hover {
+ text-decoration: underline;
+}
+
+.circuit-onion {
+ cursor: pointer;
+}
+
+.circuit-onion:hover::after {
+ opacity: 1;
+}
+
+.circuit-onion::after {
+ background-color: var(--button-bgcolor);;
+ border-radius: 4px;
+ color: var(--button-color);
+ content: attr(data-text-clicktocopy);
+ font-size: 80%;
+ opacity: 0;
+ padding: 5px 10px;
+ margin: 10px;
+ text-align: center;
+ transition: opacity 0.3s cubic-bezier(0.07, 0.95, 0, 1);
+}
+
+.circuit-onion:hover::after {
+ background-color: var(--button-hover-bgcolor);
+}
+
+.circuit-onion:active::after {
+ background-color: var(--button-active-bgcolor);
+}
+
+
+.circuit-onion-copied::after {
+ content: attr(data-text-copied);
+ opacity: 1;
+}
diff --git a/toolkit/torbutton/chrome/skin/tor-circuit-line-first.svg b/toolkit/torbutton/chrome/skin/tor-circuit-line-first.svg
new file mode 100644
index 000000000000..1981ee875aeb
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/tor-circuit-line-first.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="32">
+ <g stroke="context-stroke" opacity="context-stroke-opacity" stroke-width="2" fill="transparent">
+ <circle cx="50%" cy="50%" r="6"/>
+ <line x1="50%" y1="22" x2="50%" y2="100%"/>
+ </g>
+</svg>
diff --git a/toolkit/torbutton/chrome/skin/tor-circuit-line-last.svg b/toolkit/torbutton/chrome/skin/tor-circuit-line-last.svg
new file mode 100644
index 000000000000..19c4adaae2d2
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/tor-circuit-line-last.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="32">
+ <g stroke="context-stroke" opacity="context-stroke-opacity" stroke-width="2" fill="transparent">
+ <line x1="50%" y1="0%" x2="50%" y2="10"/>
+ <circle cx="50%" cy="50%" r="6"/>
+ </g>
+</svg>
diff --git a/toolkit/torbutton/chrome/skin/tor-circuit-line.svg b/toolkit/torbutton/chrome/skin/tor-circuit-line.svg
new file mode 100644
index 000000000000..00717f6c2054
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/tor-circuit-line.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="32">
+ <g stroke="context-stroke" opacity="context-stroke-opacity" stroke-width="2" fill="transparent">
+ <line x1="50%" y1="0%" x2="50%" y2="10"/>
+ <circle cx="50%" cy="50%" r="6"/>
+ <line x1="50%" y1="22" x2="50%" y2="100%"/>
+ </g>
+</svg>
diff --git a/toolkit/torbutton/chrome/skin/tor.png b/toolkit/torbutton/chrome/skin/tor.png
new file mode 100644
index 000000000000..75b86bfb940a
Binary files /dev/null and b/toolkit/torbutton/chrome/skin/tor.png differ
diff --git a/toolkit/torbutton/chrome/skin/torbrowser_mobile_logo.png b/toolkit/torbutton/chrome/skin/torbrowser_mobile_logo.png
new file mode 100644
index 000000000000..9d8317f02b2c
Binary files /dev/null and b/toolkit/torbutton/chrome/skin/torbrowser_mobile_logo.png differ
diff --git a/toolkit/torbutton/chrome/skin/torbutton.css b/toolkit/torbutton/chrome/skin/torbutton.css
new file mode 100644
index 000000000000..fff1714bcee5
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/torbutton.css
@@ -0,0 +1,14 @@
+svg.circuit text {
+ font-family: Arial;
+}
+
+svg#tor-circuit text.node-text {
+ dominant-baseline: central;
+ font-size: 14px;
+}
+
+svg#tor-circuit circle.node-circle {
+ stroke: #195021;
+ stroke-width: 2px;
+ fill: white;
+}
\ No newline at end of file
diff --git a/toolkit/torbutton/chrome/skin/torbutton.svg b/toolkit/torbutton/chrome/skin/torbutton.svg
new file mode 100644
index 000000000000..30cd52ba5c51
--- /dev/null
+++ b/toolkit/torbutton/chrome/skin/torbutton.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="M12.0246161,21.8174863 L12.0246161,20.3628098 C16.6324777,20.3495038 20.3634751,16.6108555 20.3634751,11.9996673 C20.3634751,7.38881189 16.6324777,3.65016355 12.0246161,3.63685757 L12.0246161,2.18218107 C17.4358264,2.1958197 21.8178189,6.58546322 21.8178189,11.9996673 C21.8178189,17.4142042 17.4358264,21.8041803 12.0246161,21.8174863 L12.0246161,21.8174863 Z M12.0246161,16.7259522 C14.623607,16.7123136 16.7272828,14.60231 [...]
+</svg>
\ No newline at end of file
diff --git a/toolkit/torbutton/components/domain-isolator.js b/toolkit/torbutton/components/domain-isolator.js
new file mode 100644
index 000000000000..1c77b5776f0f
--- /dev/null
+++ b/toolkit/torbutton/components/domain-isolator.js
@@ -0,0 +1,228 @@
+// # domain-isolator.js
+// A component for TorBrowser that puts requests from different
+// first party domains on separate tor circuits.
+
+// This file is written in call stack order (later functions
+// call earlier functions). The code file can be processed
+// with docco.js to provide clear documentation.
+
+// ### Abbreviations
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
+});
+
+// Make the logger available.
+let logger = Cc["@torproject.org/torbutton-logger;1"].getService(Ci.nsISupports)
+ .wrappedJSObject;
+
+// Import crypto object (FF 37+).
+Cu.importGlobalProperties(["crypto"]);
+
+// ## mozilla namespace.
+// Useful functionality for interacting with Mozilla services.
+let mozilla = {};
+
+// __mozilla.protocolProxyService__.
+// Mozilla's protocol proxy service, useful for managing proxy connections made
+// by the browser.
+mozilla.protocolProxyService = Cc[
+ "@mozilla.org/network/protocol-proxy-service;1"
+].getService(Ci.nsIProtocolProxyService);
+
+// __mozilla.registerProxyChannelFilter(filterFunction, positionIndex)__.
+// Registers a proxy channel filter with the Mozilla Protocol Proxy Service,
+// which will help to decide the proxy to be used for a given channel.
+// The filterFunction should expect two arguments, (aChannel, aProxy),
+// where aProxy is the proxy or list of proxies that would be used by default
+// for the given channel, and should return a new Proxy or list of Proxies.
+mozilla.registerProxyChannelFilter = function(filterFunction, positionIndex) {
+ let proxyFilter = {
+ applyFilter(aChannel, aProxy, aCallback) {
+ aCallback.onProxyFilterResult(filterFunction(aChannel, aProxy));
+ },
+ };
+ mozilla.protocolProxyService.registerChannelFilter(
+ proxyFilter,
+ positionIndex
+ );
+};
+
+// ## tor functionality.
+let tor = {};
+
+// __tor.noncesForDomains__.
+// A mutable map that records what nonce we are using for each domain.
+tor.noncesForDomains = {};
+
+// __tor.isolationEabled__.
+// A bool that controls if we use SOCKS auth for isolation or not.
+tor.isolationEnabled = true;
+
+// __tor.unknownDirtySince__.
+// Specifies when the current catch-all circuit was first used
+tor.unknownDirtySince = Date.now();
+
+// __tor.socksProxyCredentials(originalProxy, domain)__.
+// Takes a proxyInfo object (originalProxy) and returns a new proxyInfo
+// object with the same properties, except the username is set to the
+// the domain, and the password is a nonce.
+tor.socksProxyCredentials = function(originalProxy, domain) {
+ // Check if we already have a nonce. If not, create
+ // one for this domain.
+ if (!tor.noncesForDomains.hasOwnProperty(domain)) {
+ tor.noncesForDomains[domain] = tor.nonce();
+ }
+ let proxy = originalProxy.QueryInterface(Ci.nsIProxyInfo);
+ return mozilla.protocolProxyService.newProxyInfoWithAuth(
+ "socks",
+ proxy.host,
+ proxy.port,
+ domain, // username
+ tor.noncesForDomains[domain], // password
+ "", // aProxyAuthorizationHeader
+ "", // aConnectionIsolationKey
+ proxy.flags,
+ proxy.failoverTimeout,
+ proxy.failoverProxy
+ );
+};
+
+tor.nonce = function() {
+ // Generate a new 128 bit random tag. Strictly speaking both using a
+ // cryptographic entropy source and using 128 bits of entropy for the
+ // tag are likely overkill, as correct behavior only depends on how
+ // unlikely it is for there to be a collision.
+ let tag = new Uint8Array(16);
+ crypto.getRandomValues(tag);
+
+ // Convert the tag to a hex string.
+ let tagStr = "";
+ for (let i = 0; i < tag.length; i++) {
+ tagStr += (tag[i] >>> 4).toString(16);
+ tagStr += (tag[i] & 0x0f).toString(16);
+ }
+
+ return tagStr;
+};
+
+tor.newCircuitForDomain = function(domain) {
+ // Re-generate the nonce for the domain.
+ if (domain === "") {
+ domain = "--unknown--";
+ }
+ tor.noncesForDomains[domain] = tor.nonce();
+ logger.eclog(
+ 3,
+ "New domain isolation for " + domain + ": " + tor.noncesForDomains[domain]
+ );
+};
+
+// __tor.clearIsolation()_.
+// Clear the isolation state cache, forcing new circuits to be used for all
+// subsequent requests.
+tor.clearIsolation = function() {
+ // Per-domain nonces are stored in a map, so simply re-initialize the map.
+ tor.noncesForDomains = {};
+
+ // Force a rotation on the next catch-all circuit use by setting the creation
+ // time to the epoch.
+ tor.unknownDirtySince = 0;
+};
+
+// __tor.isolateCircuitsByDomain()__.
+// For every HTTPChannel, replaces the default SOCKS proxy with one that authenticates
+// to the SOCKS server (the tor client process) with a username (the first party domain)
+// and a nonce password. Tor provides a separate circuit for each username+password
+// combination.
+tor.isolateCircuitsByDomain = function() {
+ mozilla.registerProxyChannelFilter(function(aChannel, aProxy) {
+ if (!tor.isolationEnabled) {
+ return aProxy;
+ }
+ try {
+ let channel = aChannel.QueryInterface(Ci.nsIChannel),
+ firstPartyDomain = channel.loadInfo.originAttributes.firstPartyDomain;
+ if (firstPartyDomain === "") {
+ firstPartyDomain = "--unknown--";
+ if (Date.now() - tor.unknownDirtySince > 1000 * 10 * 60) {
+ logger.eclog(
+ 3,
+ "tor catchall circuit has been dirty for over 10 minutes. Rotating."
+ );
+ tor.newCircuitForDomain("--unknown--");
+ tor.unknownDirtySince = Date.now();
+ }
+ }
+ let replacementProxy = tor.socksProxyCredentials(
+ aProxy,
+ firstPartyDomain
+ );
+ logger.eclog(
+ 3,
+ `tor SOCKS: ${channel.URI.spec} via
+ ${replacementProxy.username}:${replacementProxy.password}`
+ );
+ return replacementProxy;
+ } catch (e) {
+ logger.eclog(4, `tor domain isolator error: ${e.message}`);
+ return null;
+ }
+ }, 0);
+};
+
+// ## XPCOM component construction.
+// Module specific constants
+const kMODULE_NAME = "TorBrowser Domain Isolator";
+const kMODULE_CONTRACTID = "@torproject.org/domain-isolator;1";
+const kMODULE_CID = Components.ID("e33fd6d4-270f-475f-a96f-ff3140279f68");
+
+// DomainIsolator object.
+function DomainIsolator() {
+ this.wrappedJSObject = this;
+}
+
+// Firefox component requirements
+DomainIsolator.prototype = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
+ classDescription: kMODULE_NAME,
+ classID: kMODULE_CID,
+ contractID: kMODULE_CONTRACTID,
+ observe(subject, topic, data) {
+ if (topic === "profile-after-change") {
+ logger.eclog(3, "domain isolator: set up isolating circuits by domain");
+
+ if (Services.prefs.getBoolPref("extensions.torbutton.use_nontor_proxy")) {
+ tor.isolationEnabled = false;
+ }
+ tor.isolateCircuitsByDomain();
+ }
+ },
+ newCircuitForDomain(domain) {
+ tor.newCircuitForDomain(domain);
+ },
+
+ enableIsolation() {
+ tor.isolationEnabled = true;
+ },
+
+ disableIsolation() {
+ tor.isolationEnabled = false;
+ },
+
+ clearIsolation() {
+ tor.clearIsolation();
+ },
+
+ wrappedJSObject: null,
+};
+
+// Assign factory to global object.
+const NSGetFactory = XPCOMUtils.generateNSGetFactory
+ ? XPCOMUtils.generateNSGetFactory([DomainIsolator])
+ : ComponentUtils.generateNSGetFactory([DomainIsolator]);
diff --git a/toolkit/torbutton/components/dragDropFilter.js b/toolkit/torbutton/components/dragDropFilter.js
new file mode 100644
index 000000000000..f763595be5e6
--- /dev/null
+++ b/toolkit/torbutton/components/dragDropFilter.js
@@ -0,0 +1,134 @@
+/*************************************************************************
+ * Drag and Drop Handler.
+ *
+ * Implements an observer that filters drag events to prevent OS
+ * access to URLs (a potential proxy bypass vector).
+ *************************************************************************/
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
+});
+
+// Module specific constants
+const kMODULE_NAME = "Torbutton Drag and Drop Handler";
+const kCONTRACT_ID = "@torproject.org/torbutton-dragDropFilter;1";
+const kMODULE_CID = Components.ID("f605ec27-d867-44b5-ad97-2a29276642c3");
+
+const kInterfaces = [Ci.nsIObserver, Ci.nsIClassInfo];
+
+const URLISH_TYPES = Object.freeze([
+ "text/x-moz-url",
+ "text/x-moz-url-data",
+ "text/uri-list",
+ "application/x-moz-file-promise-url",
+]);
+
+/*
+ Returns true if the text resembles a URL or even just a hostname
+ in a way that may prompt the O.S. or other applications to send out a
+ validation DNS query, if not a full request (e.g. " torproject.org",
+ even with the leading whitespace).
+*/
+function isURLish(text) {
+ // Ignore leading whitespace.
+ text = text.trim();
+
+ // Without any protocol or dot in the first chunk, this is unlikely
+ // to be considered URLish (exception: localhost, but we don't care).
+ if (!/^[a-z][a-z0-9+-]*:\/\//i.test(text)) {
+ // no protocol
+ if (!/^[^.\s\/]+\.[^.\s\/]/.test(text)) {
+ // no dot
+ return false;
+ }
+ // Prepare for hostname validation via relative URL building.
+ text = `//${text}`;
+ }
+ // Validate URL or hostname.
+ try {
+ new URL(text, "https://localhost");
+ return true;
+ } catch (e) {
+ // invalid URL, bail out
+ }
+ return false;
+}
+
+// Returns true if any chunk of text is URLish
+const hasURLish = text => text.split(/[^\p{L}_.-:\/%~@$-]+/u).some(isURLish);
+
+function DragDropFilter() {
+ this.logger = Cc["@torproject.org/torbutton-logger;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ this.logger.log(3, "Component Load 0: New DragDropFilter.");
+
+ try {
+ Services.obs.addObserver(this, "on-datatransfer-available");
+ } catch (e) {
+ this.logger.log(5, "Failed to register drag observer");
+ }
+}
+
+DragDropFilter.prototype = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
+
+ // make this an nsIClassInfo object
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ classDescription: kMODULE_NAME,
+ contractID: kCONTRACT_ID,
+ classID: kMODULE_CID,
+
+ // method of nsIClassInfo
+ getInterfaces(count) {
+ count.value = kInterfaces.length;
+ return kInterfaces;
+ },
+
+ // method of nsIClassInfo
+ getHelperForLanguage(count) {
+ return null;
+ },
+
+ // method of nsIObserver
+ observe(subject, topic, data) {
+ if (topic === "on-datatransfer-available") {
+ this.logger.log(3, "The DataTransfer is available");
+ this.filterDataTransferURLs(subject);
+ }
+ },
+
+ filterDataTransferURLs(aDataTransfer) {
+ for (let i = 0, count = aDataTransfer.mozItemCount; i < count; ++i) {
+ this.logger.log(3, `Inspecting the data transfer: ${i}.`);
+ const types = aDataTransfer.mozTypesAt(i);
+ for (const type of types) {
+ this.logger.log(3, `Type is: ${type}.`);
+ if (
+ URLISH_TYPES.includes(type) ||
+ ((type === "text/plain" || type === "text/html") &&
+ hasURLish(aDataTransfer.getData(type)))
+ ) {
+ this.logger.log(
+ 3,
+ `Removing transfer data ${aDataTransfer.getData(type)}`
+ );
+ for (const type of types) {
+ aDataTransfer.clearData(type);
+ }
+ break;
+ }
+ }
+ }
+ },
+};
+
+// Assign factory to global object.
+const NSGetFactory = XPCOMUtils.generateNSGetFactory
+ ? XPCOMUtils.generateNSGetFactory([DragDropFilter])
+ : ComponentUtils.generateNSGetFactory([DragDropFilter]);
diff --git a/toolkit/torbutton/components/external-app-blocker.js b/toolkit/torbutton/components/external-app-blocker.js
new file mode 100644
index 000000000000..6a53fc01eaf9
--- /dev/null
+++ b/toolkit/torbutton/components/external-app-blocker.js
@@ -0,0 +1,160 @@
+// Bug 1506 Android P1/TBB P5: This code provides users with notification
+// in the event of external app launch. We want it to exist in the desktop
+// port, but it is probably useless for Android.
+
+/*************************************************************************
+ * External App Handler.
+ * Handles displaying confirmation dialogs for external apps and protocols
+ * due to Firefox Bug https://bugzilla.mozilla.org/show_bug.cgi?id=440892
+ *
+ * An instance of this module is created each time the browser starts to
+ * download a file and when an external application may be invoked to
+ * handle an URL (e.g., when the user clicks on a mailto: URL).
+ *************************************************************************/
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { PromptUtils } = ChromeUtils.import(
+ "resource://gre/modules/SharedPromptUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
+});
+
+let { torbutton_get_property_string } = ChromeUtils.import(
+ "resource://torbutton/modules/utils.js"
+);
+
+// Module specific constants
+const kMODULE_NAME = "Torbutton External App Handler";
+const kCONTRACT_ID = "@torproject.org/torbutton-extAppBlocker;1";
+const kMODULE_CID = Components.ID("3da0269f-fc29-4e9e-a678-c3b1cafcf13f");
+
+const kInterfaces = [Ci.nsIObserver, Ci.nsIClassInfo];
+
+function ExternalAppBlocker() {
+ this.logger = Cc["@torproject.org/torbutton-logger;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ this.logger.log(3, "Component Load 0: New ExternalAppBlocker.");
+}
+
+ExternalAppBlocker.prototype = {
+ _helperAppLauncher: undefined,
+
+ QueryInterface: ChromeUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsIHelperAppWarningDialog,
+ ]),
+
+ // make this an nsIClassInfo object
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ classDescription: kMODULE_NAME,
+ contractID: kCONTRACT_ID,
+ classID: kMODULE_CID,
+
+ // method of nsIClassInfo
+ getInterfaces(count) {
+ count.value = kInterfaces.length;
+ return kInterfaces;
+ },
+
+ // method of nsIClassInfo
+ getHelperForLanguage(count) {
+ return null;
+ },
+
+ // method of nsIHelperAppWarningDialog
+ maybeShow(aLauncher, aWindowContext) {
+ // Hold a reference to the object that called this component. This is
+ // important not just because we need to later invoke the
+ // continueRequest() or cancelRequest() callback on aLauncher, but also
+ // so that the launcher object (which is a reference counted object) is
+ // not released too soon.
+ this._helperAppLauncher = aLauncher;
+
+ if (!Services.prefs.getBoolPref("extensions.torbutton.launch_warning")) {
+ this._helperAppLauncher.continueRequest();
+ return;
+ }
+
+ this._showPrompt(aWindowContext);
+ },
+
+ /*
+ * The _showPrompt() implementation uses some XUL and JS that is part of the
+ * browser's confirmEx() implementation. Specifically, _showPrompt() depends
+ * on chrome://global/content/commonDialog.xhtml as well as some of the code
+ * in resource://gre/modules/SharedPromptUtils.jsm.
+ */
+ _showPrompt(aWindowContext) {
+ let parentWin;
+ try {
+ parentWin = aWindowContext.getInterface(Ci.nsIDOMWindow);
+ } catch (e) {
+ parentWin = Services.wm.getMostRecentWindow("navigator:browser");
+ }
+
+ let title = torbutton_get_property_string("torbutton.popup.external.title");
+ let app = torbutton_get_property_string("torbutton.popup.external.app");
+ let note = torbutton_get_property_string("torbutton.popup.external.note");
+ let suggest = torbutton_get_property_string(
+ "torbutton.popup.external.suggest"
+ );
+ let launch = torbutton_get_property_string("torbutton.popup.launch");
+ let cancel = torbutton_get_property_string("torbutton.popup.cancel");
+ let dontask = torbutton_get_property_string("torbutton.popup.dontask");
+
+ let args = {
+ promptType: "confirmEx",
+ title,
+ text: app + note + suggest + " ",
+ checkLabel: dontask,
+ checked: false,
+ ok: false,
+ button0Label: launch,
+ button1Label: cancel,
+ defaultButtonNum: 1, // Cancel
+ buttonNumClicked: 1, // Cancel
+ enableDelay: true,
+ };
+
+ let propBag = PromptUtils.objectToPropBag(args);
+ let uri = "chrome://global/content/commonDialog.xhtml";
+ let promptWin = Services.ww.openWindow(
+ parentWin,
+ uri,
+ "_blank",
+ "centerscreen,chrome,titlebar",
+ propBag
+ );
+ promptWin.addEventListener("load", aEvent => {
+ promptWin.addEventListener("unload", aEvent => {
+ PromptUtils.propBagToObject(propBag, args);
+
+ if (0 == args.buttonNumClicked) {
+ // Save the checkbox value and tell the browser's external helper app
+ // module about the user's choice.
+ if (args.checked) {
+ Services.prefs.setBoolPref(
+ "extensions.torbutton.launch_warning",
+ false
+ );
+ }
+
+ this._helperAppLauncher.continueRequest();
+ } else {
+ this._helperAppLauncher.cancelRequest(Cr.NS_BINDING_ABORTED);
+ }
+ });
+ });
+ },
+};
+
+// Assign factory to global object.
+const NSGetFactory = XPCOMUtils.generateNSGetFactory
+ ? XPCOMUtils.generateNSGetFactory([ExternalAppBlocker])
+ : ComponentUtils.generateNSGetFactory([ExternalAppBlocker]);
diff --git a/toolkit/torbutton/components/startup-observer.js b/toolkit/torbutton/components/startup-observer.js
new file mode 100644
index 000000000000..8f9b8cc24bc3
--- /dev/null
+++ b/toolkit/torbutton/components/startup-observer.js
@@ -0,0 +1,196 @@
+// Bug 1506 P1-3: This code is mostly hackish remnants of session store
+// support. There are a couple of observer events that *might* be worth
+// listening to. Search for 1506 in the code.
+
+/*************************************************************************
+ * Startup observer (JavaScript XPCOM component)
+ *
+ * Cases tested (each during Tor and Non-Tor, FF4 and FF3.6)
+ * 1. Crash
+ * 2. Upgrade
+ * 3. Fresh install
+ *
+ *************************************************************************/
+
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+const { TorProtocolService } = ChromeUtils.import(
+ "resource://gre/modules/TorProtocolService.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
+});
+
+// Module specific constants
+const kMODULE_NAME = "Startup";
+const kMODULE_CONTRACTID = "@torproject.org/startup-observer;1";
+const kMODULE_CID = Components.ID("06322def-6fde-4c06-aef6-47ae8e799629");
+
+function cleanupCookies() {
+ const migratedPref = "extensions.torbutton.cookiejar_migrated";
+ if (!Services.prefs.getBoolPref(migratedPref, false)) {
+ // Cleanup stored cookie-jar-selector json files
+ const profileFolder = Services.dirsvc.get("ProfD", Ci.nsIFile).clone();
+ for (const file of profileFolder.directoryEntries) {
+ if (file.leafName.match(/^(cookies|protected)-.*[.]json$/)) {
+ try {
+ file.remove(false);
+ } catch (e) {}
+ }
+ }
+ Services.prefs.setBoolPref(migratedPref, true);
+ }
+}
+
+function StartupObserver() {
+ this.logger = Cc["@torproject.org/torbutton-logger;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ this._prefs = Services.prefs;
+ this.logger.log(3, "Startup Observer created");
+
+ var env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ var prefName = "browser.startup.homepage";
+ if (env.exists("TOR_DEFAULT_HOMEPAGE")) {
+ // if the user has set this value in a previous installation, don't override it
+ if (!this._prefs.prefHasUserValue(prefName)) {
+ this._prefs.setCharPref(prefName, env.get("TOR_DEFAULT_HOMEPAGE"));
+ }
+ }
+
+ this.is_tbb = true;
+
+ try {
+ // XXX: We're in a race with HTTPS-Everywhere to update our proxy settings
+ // before the initial SSL-Observatory test... If we lose the race, Firefox
+ // caches the old proxy settings for check.tp.o somehwere, and it never loads :(
+ this.setProxySettings();
+ } catch (e) {
+ this.logger.log(
+ 4,
+ "Early proxy change failed. Will try again at profile load. Error: " + e
+ );
+ }
+
+ cleanupCookies();
+}
+
+StartupObserver.prototype = {
+ // Bug 6803: We need to get the env vars early due to
+ // some weird proxy caching code that showed up in FF15.
+ // Otherwise, homepage domain loads fail forever.
+ setProxySettings() {
+ if (!this.is_tbb) {
+ return;
+ }
+
+ // Bug 1506: Still want to get these env vars
+ let environ = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ if (environ.exists("TOR_TRANSPROXY")) {
+ this.logger.log(3, "Resetting Tor settings to transproxy");
+ this._prefs.setBoolPref("network.proxy.socks_remote_dns", false);
+ this._prefs.setIntPref("network.proxy.type", 0);
+ this._prefs.setIntPref("network.proxy.socks_port", 0);
+ this._prefs.setCharPref("network.proxy.socks", "");
+ } else {
+ // Try to retrieve SOCKS proxy settings from Tor Launcher.
+ let socksPortInfo;
+ try {
+ socksPortInfo = TorProtocolService.torGetSOCKSPortInfo();
+ } catch (e) {
+ this.logger.log(3, "tor launcher failed " + e);
+ }
+
+ // If Tor Launcher is not available, check environment variables.
+ if (!socksPortInfo) {
+ socksPortInfo = { ipcFile: undefined, host: undefined, port: 0 };
+
+ let isWindows = Services.appinfo.OS === "WINNT";
+ if (!isWindows && environ.exists("TOR_SOCKS_IPC_PATH")) {
+ socksPortInfo.ipcFile = new FileUtils.File(
+ environ.get("TOR_SOCKS_IPC_PATH")
+ );
+ } else {
+ if (environ.exists("TOR_SOCKS_HOST")) {
+ socksPortInfo.host = environ.get("TOR_SOCKS_HOST");
+ }
+ if (environ.exists("TOR_SOCKS_PORT")) {
+ socksPortInfo.port = parseInt(environ.get("TOR_SOCKS_PORT"));
+ }
+ }
+ }
+
+ // Adjust network.proxy prefs.
+ if (socksPortInfo.ipcFile) {
+ let fph = Services.io
+ .getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler);
+ let fileURI = fph.newFileURI(socksPortInfo.ipcFile);
+ this.logger.log(3, "Reset socks to " + fileURI.spec);
+ this._prefs.setCharPref("network.proxy.socks", fileURI.spec);
+ this._prefs.setIntPref("network.proxy.socks_port", 0);
+ } else {
+ if (socksPortInfo.host) {
+ this._prefs.setCharPref("network.proxy.socks", socksPortInfo.host);
+ this.logger.log(3, "Reset socks host to " + socksPortInfo.host);
+ }
+ if (socksPortInfo.port) {
+ this._prefs.setIntPref(
+ "network.proxy.socks_port",
+ socksPortInfo.port
+ );
+ this.logger.log(3, "Reset socks port to " + socksPortInfo.port);
+ }
+ }
+
+ if (socksPortInfo.ipcFile || socksPortInfo.host || socksPortInfo.port) {
+ this._prefs.setBoolPref("network.proxy.socks_remote_dns", true);
+ this._prefs.setIntPref("network.proxy.type", 1);
+ }
+ }
+
+ // Force prefs to be synced to disk
+ Services.prefs.savePrefFile(null);
+
+ this.logger.log(3, "Synced network settings to environment.");
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "profile-after-change") {
+ // Bug 1506 P1: We listen to these prefs as signals for startup,
+ // but only for hackish reasons.
+ this._prefs.setBoolPref("extensions.torbutton.startup", true);
+
+ this.setProxySettings();
+ }
+
+ // In all cases, force prefs to be synced to disk
+ Services.prefs.savePrefFile(null);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIClassInfo]),
+
+ // method of nsIClassInfo
+ classDescription: "Torbutton Startup Observer",
+ classID: kMODULE_CID,
+ contractID: kMODULE_CONTRACTID,
+
+ // Hack to get us registered early to observe recovery
+ _xpcom_categories: [{ category: "profile-after-change" }],
+};
+
+// Assign factory to global object.
+const NSGetFactory = XPCOMUtils.generateNSGetFactory
+ ? XPCOMUtils.generateNSGetFactory([StartupObserver])
+ : ComponentUtils.generateNSGetFactory([StartupObserver]);
diff --git a/toolkit/torbutton/components/torCheckService.js b/toolkit/torbutton/components/torCheckService.js
new file mode 100644
index 000000000000..41d716ff935c
--- /dev/null
+++ b/toolkit/torbutton/components/torCheckService.js
@@ -0,0 +1,140 @@
+/*************************************************************************
+ * Copyright (c) 2013, The Tor Project, Inc.
+ * See LICENSE for licensing information.
+ *
+ * vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+ *
+ * Tor check service
+ *************************************************************************/
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
+});
+
+// Module specific constants
+const kMODULE_NAME = "Torbutton Tor Check Service";
+const kMODULE_CONTRACTID = "@torproject.org/torbutton-torCheckService;1";
+const kMODULE_CID = Components.ID("5d57312b-5d8c-4169-b4af-e80d6a28a72e");
+
+function TBTorCheckService() {
+ this._logger = Cc["@torproject.org/torbutton-logger;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ this._logger.log(3, "Torbutton Tor Check Service initialized");
+
+ this._statusOfTorCheck = this.kCheckNotInitiated;
+ this.wrappedJSObject = this;
+}
+
+TBTorCheckService.prototype = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIClassInfo]),
+
+ kCheckNotInitiated: 0, // Possible values for statusOfTorCheck.
+ kCheckSuccessful: 1,
+ kCheckFailed: 2,
+
+ wrappedJSObject: null,
+ _logger: null,
+ _statusOfTorCheck: 0, // this.kCheckNotInitiated,
+
+ // make this an nsIClassInfo object
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+
+ // method of nsIClassInfo
+ classDescription: kMODULE_NAME,
+ classID: kMODULE_CID,
+ contractID: kMODULE_CONTRACTID,
+
+ // method of nsIClassInfo
+ getInterfaces(count) {
+ var interfaceList = [Ci.nsIClassInfo];
+ count.value = interfaceList.length;
+ return interfaceList;
+ },
+
+ // method of nsIClassInfo
+ getHelperForLanguage(count) {
+ return null;
+ },
+
+ // Public methods.
+ get statusOfTorCheck() {
+ return this._statusOfTorCheck;
+ },
+
+ set statusOfTorCheck(aStatus) {
+ this._statusOfTorCheck = aStatus;
+ },
+
+ createCheckRequest(aAsync) {
+ let req = new XMLHttpRequest();
+ let url = Services.prefs.getCharPref("extensions.torbutton.test_url");
+ req.open("GET", url, aAsync);
+ req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ req.overrideMimeType("text/xml");
+ req.timeout = 120000; // Wait at most two minutes for a response.
+ return req;
+ },
+
+ parseCheckResponse(aReq) {
+ let ret = 0;
+ if (aReq.status == 200) {
+ if (!aReq.responseXML) {
+ this._logger.log(5, "Check failed! Not text/xml!");
+ ret = 1;
+ } else {
+ let result = aReq.responseXML.getElementById("TorCheckResult");
+
+ if (result === null) {
+ this._logger.log(5, "Test failed! No TorCheckResult element");
+ ret = 2;
+ } else if (
+ typeof result.target == "undefined" ||
+ result.target === null
+ ) {
+ this._logger.log(5, "Test failed! No target");
+ ret = 3;
+ } else if (result.target === "success") {
+ this._logger.log(3, "Test Successful");
+ ret = 4;
+ } else if (result.target === "failure") {
+ this._logger.log(5, "Tor test failed!");
+ ret = 5;
+ } else if (result.target === "unknown") {
+ this._logger.log(5, "Tor test failed. TorDNSEL Failure?");
+ ret = 6;
+ } else {
+ this._logger.log(5, "Tor test failed. Strange target.");
+ ret = 7;
+ }
+ }
+ } else {
+ if (0 == aReq.status) {
+ try {
+ var req = aReq.channel.QueryInterface(Ci.nsIRequest);
+ if (req.status == Cr.NS_ERROR_PROXY_CONNECTION_REFUSED) {
+ this._logger.log(5, "Tor test failed. Proxy connection refused");
+ ret = 8;
+ }
+ } catch (e) {}
+ }
+
+ if (ret == 0) {
+ this._logger.log(5, "Tor test failed. HTTP Error: " + aReq.status);
+ ret = -aReq.status;
+ }
+ }
+
+ return ret;
+ },
+};
+
+// Assign factory to global object.
+const NSGetFactory = XPCOMUtils.generateNSGetFactory
+ ? XPCOMUtils.generateNSGetFactory([TBTorCheckService])
+ : ComponentUtils.generateNSGetFactory([TBTorCheckService]);
diff --git a/toolkit/torbutton/components/torbutton-logger.js b/toolkit/torbutton/components/torbutton-logger.js
new file mode 100644
index 000000000000..2fdcd7e6a753
--- /dev/null
+++ b/toolkit/torbutton/components/torbutton-logger.js
@@ -0,0 +1,185 @@
+// Bug 1506 P1: This is just a handy logger. If you have a better one, toss
+// this in the trash.
+
+/*************************************************************************
+ * TBLogger (JavaScript XPCOM component)
+ *
+ * Allows loglevel-based logging to different logging mechanisms.
+ *
+ *************************************************************************/
+
+// Module specific constants
+const kMODULE_NAME = "Torbutton Logger";
+const kMODULE_CONTRACTID = "@torproject.org/torbutton-logger;1";
+const kMODULE_CID = Components.ID("f36d72c9-9718-4134-b550-e109638331d7");
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ComponentUtils: "resource://gre/modules/ComponentUtils.jsm",
+});
+
+function TorbuttonLogger() {
+ // Register observer
+ Services.prefs.addObserver("extensions.torbutton", this);
+
+ this.loglevel = Services.prefs.getIntPref("extensions.torbutton.loglevel");
+ this.logmethod = Services.prefs.getIntPref("extensions.torbutton.logmethod");
+
+ try {
+ var logMngr = Cc["@mozmonkey.com/debuglogger/manager;1"].getService(
+ Ci.nsIDebugLoggerManager
+ );
+ this._debuglog = logMngr.registerLogger("torbutton");
+ } catch (exErr) {
+ this._debuglog = false;
+ }
+ this._console = Services.console;
+
+ // This JSObject is exported directly to chrome
+ this.wrappedJSObject = this;
+ this.log(3, "Torbutton debug output ready");
+}
+
+/**
+ * JS XPCOM component registration goop:
+ *
+ * Everything below is boring boilerplate and can probably be ignored.
+ */
+
+const nsIClassInfo = Ci.nsIClassInfo;
+
+const logString = { 1: "VERB", 2: "DBUG", 3: "INFO", 4: "NOTE", 5: "WARN" };
+
+function padInt(i) {
+ return i < 10 ? "0" + i : i;
+}
+
+TorbuttonLogger.prototype = {
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIClassInfo]),
+
+ wrappedJSObject: null, // Initialized by constructor
+
+ // make this an nsIClassInfo object
+ flags: nsIClassInfo.DOM_OBJECT,
+
+ // method of nsIClassInfo
+ classDescription: "TorbuttonLogger",
+ classID: kMODULE_CID,
+ contractID: kMODULE_CONTRACTID,
+
+ // method of nsIClassInfo
+ getInterfaces(count) {
+ var interfaceList = [nsIClassInfo];
+ count.value = interfaceList.length;
+ return interfaceList;
+ },
+
+ // method of nsIClassInfo
+ getHelperForLanguage(count) {
+ return null;
+ },
+
+ formatLog(str, level) {
+ var d = new Date();
+ var now =
+ padInt(d.getUTCMonth() + 1) +
+ "-" +
+ padInt(d.getUTCDate()) +
+ " " +
+ padInt(d.getUTCHours()) +
+ ":" +
+ padInt(d.getUTCMinutes()) +
+ ":" +
+ padInt(d.getUTCSeconds());
+ return "[" + now + "] Torbutton " + logString[level] + ": " + str;
+ },
+
+ // error console log
+ eclog(level, str) {
+ switch (this.logmethod) {
+ case 0: // stderr
+ if (this.loglevel <= level) {
+ dump(this.formatLog(str, level) + "\n");
+ }
+ break;
+ default:
+ // errorconsole
+ if (this.loglevel <= level) {
+ this._console.logStringMessage(this.formatLog(str, level));
+ }
+ break;
+ }
+ },
+
+ safe_log(level, str, scrub) {
+ if (this.loglevel < 4) {
+ this.eclog(level, str + scrub);
+ } else {
+ this.eclog(level, str + " [scrubbed]");
+ }
+ },
+
+ log(level, str) {
+ switch (this.logmethod) {
+ case 2: // debuglogger
+ if (this._debuglog) {
+ this._debuglog.log(6 - level, this.formatLog(str, level));
+ break;
+ }
+ // fallthrough
+ case 0: // stderr
+ if (this.loglevel <= level) {
+ dump(this.formatLog(str, level) + "\n");
+ }
+ break;
+ case 1: // errorconsole
+ if (this.loglevel <= level) {
+ this._console.logStringMessage(this.formatLog(str, level));
+ }
+ break;
+ default:
+ dump("Bad log method: " + this.logmethod);
+ }
+ },
+
+ // Pref observer interface implementation
+
+ // topic: what event occurred
+ // subject: what nsIPrefBranch we're observing
+ // data: which pref has been changed (relative to subject)
+ observe(subject, topic, data) {
+ if (topic != "nsPref:changed") {
+ return;
+ }
+ switch (data) {
+ case "extensions.torbutton.logmethod":
+ this.logmethod = Services.prefs.getIntPref(
+ "extensions.torbutton.logmethod"
+ );
+ if (this.logmethod === 0) {
+ Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
+ } else if (
+ Services.prefs.getIntPref("extensions.torlauncher.logmethod", 3) !== 0
+ ) {
+ // If Tor Launcher is not available or its log method is not 0
+ // then let's reset the dump pref.
+ Services.prefs.setBoolPref("browser.dom.window.dump.enabled", false);
+ }
+ break;
+ case "extensions.torbutton.loglevel":
+ this.loglevel = Services.prefs.getIntPref(
+ "extensions.torbutton.loglevel"
+ );
+ break;
+ }
+ },
+};
+
+// Assign factory to global object.
+const NSGetFactory = XPCOMUtils.generateNSGetFactory
+ ? XPCOMUtils.generateNSGetFactory([TorbuttonLogger])
+ : ComponentUtils.generateNSGetFactory([TorbuttonLogger]);
diff --git a/toolkit/torbutton/jar.mn b/toolkit/torbutton/jar.mn
new file mode 100644
index 000000000000..5e7a11384cc4
--- /dev/null
+++ b/toolkit/torbutton/jar.mn
@@ -0,0 +1,46 @@
+#filter substitution
+
+torbutton.jar:
+
+% content torbutton %content/
+
+ content/torbutton.js (chrome/content/torbutton.js)
+ content/tor-circuit-display.js (chrome/content/tor-circuit-display.js)
+ content/preferences.xhtml (chrome/content/preferences.xhtml)
+ content/aboutTor/aboutTor-content.js (chrome/content/aboutTor/aboutTor-content.js)
+* content/aboutTor/aboutTor.xhtml (chrome/content/aboutTor/aboutTor.xhtml)
+ content/aboutTor/resources/aboutTor.js (chrome/content/aboutTor/resources/aboutTor.js)
+ content/preferences-mobile.js (chrome/content/preferences-mobile.js)
+
+ components/ (components/*)
+ modules/ (modules/*)
+ skin/ (chrome/skin/*)
+
+% resource torbutton %
+% resource torbutton-abouttor resource://torbutton/content/aboutTor/resources/ contentaccessible=yes
+% resource torbutton-assets resource://torbutton/skin/ contentaccessible=yes
+
+% skin torbutton classic/1.0 %skin/
+
+# Firefox 4-style component registration
+% component {f605ec27-d867-44b5-ad97-2a29276642c3} %components/dragDropFilter.js
+% contract @torproject.org/torbutton-dragDropFilter;1 {f605ec27-d867-44b5-ad97-2a29276642c3}
+
+% component {3da0269f-fc29-4e9e-a678-c3b1cafcf13f} %components/external-app-blocker.js
+% contract @torproject.org/torbutton-extAppBlocker;1 {3da0269f-fc29-4e9e-a678-c3b1cafcf13f}
+
+% component {06322def-6fde-4c06-aef6-47ae8e799629} %components/startup-observer.js
+% contract @torproject.org/startup-observer;1 {06322def-6fde-4c06-aef6-47ae8e799629}
+
+% component {5d57312b-5d8c-4169-b4af-e80d6a28a72e} %components/torCheckService.js
+% contract @torproject.org/torbutton-torCheckService;1 {5d57312b-5d8c-4169-b4af-e80d6a28a72e}
+
+% component {f36d72c9-9718-4134-b550-e109638331d7} %components/torbutton-logger.js
+% contract @torproject.org/torbutton-logger;1 {f36d72c9-9718-4134-b550-e109638331d7}
+
+% component {e33fd6d4-270f-475f-a96f-ff3140279f68} %components/domain-isolator.js
+% contract @torproject.org/domain-isolator;1 {e33fd6d4-270f-475f-a96f-ff3140279f68}
+
+% category profile-after-change StartupObserver @torproject.org/startup-observer;1
+% category profile-after-change DomainIsolator @torproject.org/domain-isolator;1
+% category profile-after-change DragDropFilter @torproject.org/torbutton-dragDropFilter;1
diff --git a/toolkit/torbutton/modules/tor-control-port.js b/toolkit/torbutton/modules/tor-control-port.js
new file mode 100644
index 000000000000..374ff5fd1bfd
--- /dev/null
+++ b/toolkit/torbutton/modules/tor-control-port.js
@@ -0,0 +1,982 @@
+// A module for TorBrowser that provides an asynchronous controller for
+// Tor, through its ControlPort.
+//
+// This file is written in call stack order (later functions
+// call earlier functions). The file can be processed
+// with docco.js to produce pretty documentation.
+//
+// To import the module, use
+//
+// let { configureControlPortModule, controller, wait_for_controller } =
+// Components.utils.import("path/to/tor-control-port.js", {});
+//
+// See the third-to-last function defined in this file:
+// configureControlPortModule(ipcFile, host, port, password)
+// for usage of the configureControlPortModule function.
+//
+// See the last functions defined in this file:
+// controller(avoidCache), wait_for_controller(avoidCache)
+// for usage of the controller functions.
+
+/* jshint esnext: true */
+/* jshint -W097 */
+/* global console */
+"use strict";
+
+// ### Import Mozilla Services
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorMonitorService",
+ "resource://gre/modules/TorMonitorService.jsm"
+);
+
+// tor-launcher observer topics
+const TorTopics = Object.freeze({
+ ProcessIsReady: "TorProcessIsReady",
+});
+
+// __log__.
+// Logging function
+let logger = Cc["@torproject.org/torbutton-logger;1"].getService(Ci.nsISupports)
+ .wrappedJSObject;
+let log = x => logger.eclog(3, x.trimRight().replace(/\r\n/g, "\n"));
+
+// ### announce this file
+log("Loading tor-control-port.js\n");
+
+class AsyncSocket {
+ constructor(ipcFile, host, port) {
+ let sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
+ Ci.nsISocketTransportService
+ );
+ const OPEN_UNBUFFERED = Ci.nsITransport.OPEN_UNBUFFERED;
+
+ let socketTransport = ipcFile
+ ? sts.createUnixDomainTransport(ipcFile)
+ : sts.createTransport([], host, port, null, null);
+
+ this.outputStream = socketTransport
+ .openOutputStream(OPEN_UNBUFFERED, 1, 1)
+ .QueryInterface(Ci.nsIAsyncOutputStream);
+ this.outputQueue = [];
+
+ this.inputStream = socketTransport
+ .openInputStream(OPEN_UNBUFFERED, 1, 1)
+ .QueryInterface(Ci.nsIAsyncInputStream);
+ this.scriptableInputStream = Cc[
+ "@mozilla.org/scriptableinputstream;1"
+ ].createInstance(Ci.nsIScriptableInputStream);
+ this.scriptableInputStream.init(this.inputStream);
+ this.inputQueue = [];
+ }
+
+ // asynchronously write string to underlying socket and return number of bytes written
+ async write(str) {
+ return new Promise((resolve, reject) => {
+ // asyncWait next write request
+ const tryAsyncWait = () => {
+ if (this.outputQueue.length) {
+ this.outputStream.asyncWait(
+ this.outputQueue.at(0), // next request
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ }
+ };
+
+ // output stream can only have 1 registered callback at a time, so multiple writes
+ // need to be queued up (see nsIAsyncOutputStream.idl)
+ this.outputQueue.push({
+ // Implement an nsIOutputStreamCallback:
+ onOutputStreamReady: () => {
+ try {
+ let bytesWritten = this.outputStream.write(str, str.length);
+
+ // remove this callback object from queue as it is now completed
+ this.outputQueue.shift();
+
+ // request next wait if there is one
+ tryAsyncWait();
+
+ // finally resolve promise
+ resolve(bytesWritten);
+ } catch (err) {
+ // reject promise on error
+ reject(err);
+ }
+ },
+ });
+
+ // length 1 imples that there is no in-flight asyncWait, so we may immediately
+ // follow through on this write
+ if (this.outputQueue.length == 1) {
+ tryAsyncWait();
+ }
+ });
+ }
+
+ // asynchronously read string from underlying socket and return it
+ async read() {
+ return new Promise((resolve, reject) => {
+ const tryAsyncWait = () => {
+ if (this.inputQueue.length) {
+ this.inputStream.asyncWait(
+ this.inputQueue.at(0), // next input request
+ 0,
+ 0,
+ Services.tm.currentThread
+ );
+ }
+ };
+
+ this.inputQueue.push({
+ onInputStreamReady: stream => {
+ try {
+ // read our string from input stream
+ let str = this.scriptableInputStream.read(
+ this.scriptableInputStream.available()
+ );
+
+ // remove this callback object from queue now that we have read
+ this.inputQueue.shift();
+
+ // request next wait if there is one
+ tryAsyncWait();
+
+ // finally resolve promise
+ resolve(str);
+ } catch (err) {
+ reject(err);
+ }
+ },
+ });
+
+ // length 1 imples that there is no in-flight asyncWait, so we may immediately
+ // follow through on this read
+ if (this.inputQueue.length == 1) {
+ tryAsyncWait();
+ }
+ });
+ }
+
+ close() {
+ this.outputStream.close();
+ this.inputStream.close();
+ }
+}
+
+class ControlSocket {
+ constructor(asyncSocket) {
+ this.socket = asyncSocket;
+ this._isOpen = true;
+ this.pendingData = "";
+ this.pendingLines = [];
+
+ this.mainDispatcher = io.callbackDispatcher();
+ this.notificationDispatcher = io.callbackDispatcher();
+ // mainDispatcher pushes only async notifications (650) to notificationDispatcher
+ this.mainDispatcher.addCallback(
+ /^650/,
+ this._handleNotification.bind(this)
+ );
+ // callback for handling responses and errors
+ this.mainDispatcher.addCallback(
+ /^[245]\d\d/,
+ this._handleCommandReply.bind(this)
+ );
+
+ this.commandQueue = [];
+
+ this._startMessagePump();
+ }
+
+ // blocks until an entire line is read and returns it
+ // immediately returns next line in queue (pendingLines) if present
+ async _readLine() {
+ // keep reading from socket until we have a full line to return
+ while (!this.pendingLines.length) {
+ // read data from our socket and spit on newline tokens
+ this.pendingData += await this.socket.read();
+ let lines = this.pendingData.split("\r\n");
+
+ // the last line will either be empty string, or a partial read of a response/event
+ // so save it off for the next socket read
+ this.pendingData = lines.pop();
+
+ // copy remaining full lines to our pendingLines list
+ this.pendingLines = this.pendingLines.concat(lines);
+ }
+ return this.pendingLines.shift();
+ }
+
+ // blocks until an entire message is ready and returns it
+ async _readMessage() {
+ // whether we are searching for the end of a multi-line values
+ // See control-spec section 3.9
+ let handlingMultlineValue = false;
+ let endOfMessageFound = false;
+ const message = [];
+
+ do {
+ const line = await this._readLine();
+ message.push(line);
+
+ if (handlingMultlineValue) {
+ // look for end of multiline
+ if (line.match(/^\.$/)) {
+ handlingMultlineValue = false;
+ }
+ } else {
+ // 'Multiline values' are possible. We avoid interrupting one by detecting it
+ // and waiting for a terminating "." on its own line.
+ // (See control-spec section 3.9 and https://trac.torproject.org/16990#comment:28
+ // Ensure this is the first line of a new message
+ // eslint-disable-next-line no-lonely-if
+ if (message.length === 1 && line.match(/^\d\d\d\+.+?=$/)) {
+ handlingMultlineValue = true;
+ }
+ // look for end of message (note the space character at end of the regex)
+ else if (line.match(/^\d\d\d /)) {
+ if (message.length == 1) {
+ endOfMessageFound = true;
+ } else {
+ let firstReplyCode = message[0].substring(0, 3);
+ let lastReplyCode = line.substring(0, 3);
+ if (firstReplyCode == lastReplyCode) {
+ endOfMessageFound = true;
+ }
+ }
+ }
+ }
+ } while (!endOfMessageFound);
+
+ // join our lines back together to form one message
+ return message.join("\r\n");
+ }
+
+ async _startMessagePump() {
+ try {
+ while (true) {
+ let message = await this._readMessage();
+ log("controlPort >> " + message);
+ this.mainDispatcher.pushMessage(message);
+ }
+ } catch (err) {
+ this._isOpen = false;
+ for (const cmd of this.commandQueue) {
+ cmd.reject(err);
+ }
+ this.commandQueue = [];
+ }
+ }
+
+ _writeNextCommand() {
+ let cmd = this.commandQueue[0];
+ log("controlPort << " + cmd.commandString);
+ this.socket.write(`${cmd.commandString}\r\n`).catch(cmd.reject);
+ }
+
+ async sendCommand(commandString) {
+ if (!this.isOpen()) {
+ throw new Error("ControlSocket not open");
+ }
+
+ // this promise is resolved either in _handleCommandReply, or
+ // in _startMessagePump (on stream error)
+ return new Promise((resolve, reject) => {
+ let command = {
+ commandString,
+ resolve,
+ reject,
+ };
+
+ this.commandQueue.push(command);
+ if (this.commandQueue.length == 1) {
+ this._writeNextCommand();
+ }
+ });
+ }
+
+ _handleCommandReply(message) {
+ let cmd = this.commandQueue.shift();
+ if (message.match(/^2/)) {
+ cmd.resolve(message);
+ } else if (message.match(/^[45]/)) {
+ let myErr = new Error(cmd.commandString + " -> " + message);
+ // Add Tor-specific information to the Error object.
+ let idx = message.indexOf(" ");
+ if (idx > 0) {
+ myErr.torStatusCode = message.substring(0, idx);
+ myErr.torMessage = message.substring(idx);
+ } else {
+ myErr.torStatusCode = message;
+ }
+ cmd.reject(myErr);
+ } else {
+ cmd.reject(
+ new Error(
+ `ControlSocket::_handleCommandReply received unexpected message:\n----\n${message}\n----`
+ )
+ );
+ }
+
+ // send next command if one is available
+ if (this.commandQueue.length) {
+ this._writeNextCommand();
+ }
+ }
+
+ _handleNotification(message) {
+ this.notificationDispatcher.pushMessage(message);
+ }
+
+ close() {
+ this.socket.close();
+ this._isOpen = false;
+ }
+
+ addNotificationCallback(regex, callback) {
+ this.notificationDispatcher.addCallback(regex, callback);
+ }
+
+ isOpen() {
+ return this._isOpen;
+ }
+}
+
+// ## io
+// I/O utilities namespace
+
+let io = {};
+
+// __io.callbackDispatcher()__.
+// Returns dispatcher object with three member functions:
+// dispatcher.addCallback(regex, callback), dispatcher.removeCallback(callback),
+// and dispatcher.pushMessage(message).
+// Pass pushMessage to another function that needs a callback with a single string
+// argument. Whenever dispatcher.pushMessage receives a string, the dispatcher will
+// check for any regex matches and pass the string on to the corresponding callback(s).
+io.callbackDispatcher = function() {
+ let callbackPairs = [],
+ removeCallback = function(aCallback) {
+ callbackPairs = callbackPairs.filter(function([regex, callback]) {
+ return callback !== aCallback;
+ });
+ },
+ addCallback = function(regex, callback) {
+ if (callback) {
+ callbackPairs.push([regex, callback]);
+ }
+ return function() {
+ removeCallback(callback);
+ };
+ },
+ pushMessage = function(message) {
+ for (let [regex, callback] of callbackPairs) {
+ if (message.match(regex)) {
+ callback(message);
+ }
+ }
+ };
+ return {
+ pushMessage,
+ removeCallback,
+ addCallback,
+ };
+};
+
+// __io.controlSocket(ipcFile, host, port, password)__.
+// Instantiates and returns a socket to a tor ControlPort at ipcFile or
+// host:port, authenticating with the given password. Example:
+//
+// // Open the socket
+// let socket = await io.controlSocket(undefined, "127.0.0.1", 9151, "MyPassw0rd");
+// // Send command and receive "250" response reply or error is thrown
+// await socket.sendCommand(commandText);
+// // Register or deregister for "650" notifications
+// // that match regex
+// socket.addNotificationCallback(regex, callback);
+// socket.removeNotificationCallback(callback);
+// // Close the socket permanently
+// socket.close();
+io.controlSocket = async function(ipcFile, host, port, password) {
+ let socket = new AsyncSocket(ipcFile, host, port);
+ let controlSocket = new ControlSocket(socket);
+
+ // Log in to control port.
+ await controlSocket.sendCommand("authenticate " + (password || ""));
+ // Activate needed events.
+ await controlSocket.sendCommand("setevents stream");
+
+ return controlSocket;
+};
+
+// ## utils
+// A namespace for utility functions
+let utils = {};
+
+// __utils.identity(x)__.
+// Returns its argument unchanged.
+utils.identity = function(x) {
+ return x;
+};
+
+// __utils.isString(x)__.
+// Returns true iff x is a string.
+utils.isString = function(x) {
+ return typeof x === "string" || x instanceof String;
+};
+
+// __utils.capture(string, regex)__.
+// Takes a string and returns an array of capture items, where regex must have a single
+// capturing group and use the suffix /.../g to specify a global search.
+utils.capture = function(string, regex) {
+ let matches = [];
+ // Special trick to use string.replace for capturing multiple matches.
+ string.replace(regex, function(a, captured) {
+ matches.push(captured);
+ });
+ return matches;
+};
+
+// __utils.extractor(regex)__.
+// Returns a function that takes a string and returns an array of regex matches. The
+// regex must use the suffix /.../g to specify a global search.
+utils.extractor = function(regex) {
+ return function(text) {
+ return utils.capture(text, regex);
+ };
+};
+
+// __utils.splitLines(string)__.
+// Splits a string into an array of strings, each corresponding to a line.
+utils.splitLines = function(string) {
+ return string.split(/\r?\n/);
+};
+
+// __utils.splitAtSpaces(string)__.
+// Splits a string into chunks between spaces. Does not split at spaces
+// inside pairs of quotation marks.
+utils.splitAtSpaces = utils.extractor(/((\S*?"(.*?)")+\S*|\S+)/g);
+
+// __utils.splitAtFirst(string, regex)__.
+// Splits a string at the first instance of regex match. If no match is
+// found, returns the whole string.
+utils.splitAtFirst = function(string, regex) {
+ let match = string.match(regex);
+ return match
+ ? [
+ string.substring(0, match.index),
+ string.substring(match.index + match[0].length),
+ ]
+ : string;
+};
+
+// __utils.splitAtEquals(string)__.
+// Splits a string into chunks between equals. Does not split at equals
+// inside pairs of quotation marks.
+utils.splitAtEquals = utils.extractor(/(([^=]*?"(.*?)")+[^=]*|[^=]+)/g);
+
+// __utils.mergeObjects(arrayOfObjects)__.
+// Takes an array of objects like [{"a":"b"},{"c":"d"}] and merges to a single object.
+// Pure function.
+utils.mergeObjects = function(arrayOfObjects) {
+ let result = {};
+ for (let obj of arrayOfObjects) {
+ for (let key in obj) {
+ result[key] = obj[key];
+ }
+ }
+ return result;
+};
+
+// __utils.listMapData(parameterString, listNames)__.
+// Takes a list of parameters separated by spaces, of which the first several are
+// unnamed, and the remainder are named, in the form `NAME=VALUE`. Apply listNames
+// to the unnamed parameters, and combine them in a map with the named parameters.
+// Example: `40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH`
+//
+// utils.listMapData("40 FAILED 0 95.78.59.36:80 REASON=CANT_ATTACH",
+// ["streamID", "event", "circuitID", "IP"])
+// // --> {"streamID" : "40", "event" : "FAILED", "circuitID" : "0",
+// // "address" : "95.78.59.36:80", "REASON" : "CANT_ATTACH"}"
+utils.listMapData = function(parameterString, listNames) {
+ // Split out the space-delimited parameters.
+ let parameters = utils.splitAtSpaces(parameterString),
+ dataMap = {};
+ // Assign listNames to the first n = listNames.length parameters.
+ for (let i = 0; i < listNames.length; ++i) {
+ dataMap[listNames[i]] = parameters[i];
+ }
+ // Read key-value pairs and copy these to the dataMap.
+ for (let i = listNames.length; i < parameters.length; ++i) {
+ let [key, value] = utils.splitAtEquals(parameters[i]);
+ if (key && value) {
+ dataMap[key] = value;
+ }
+ }
+ return dataMap;
+};
+
+// __utils.rejectPromise(errorMessage)__.
+// Returns a rejected promise with the given error message.
+utils.rejectPromise = errorMessage => Promise.reject(new Error(errorMessage));
+
+// ## info
+// A namespace for functions related to tor's GETINFO and GETCONF command.
+let info = {};
+
+// __info.keyValueStringsFromMessage(messageText)__.
+// Takes a message (text) response to GETINFO or GETCONF and provides
+// a series of key-value strings, which are either multiline (with a `250+` prefix):
+//
+// 250+config/defaults=
+// AccountingMax "0 bytes"
+// AllowDotExit "0"
+// .
+//
+// or single-line (with a `250-` or `250 ` prefix):
+//
+// 250-version=0.2.6.0-alpha-dev (git-b408125288ad6943)
+info.keyValueStringsFromMessage = utils.extractor(
+ /^(250\+[\s\S]+?^\.|250[- ].+?)$/gim
+);
+
+// __info.applyPerLine(transformFunction)__.
+// Returns a function that splits text into lines,
+// and applies transformFunction to each line.
+info.applyPerLine = function(transformFunction) {
+ return function(text) {
+ return utils.splitLines(text.trim()).map(transformFunction);
+ };
+};
+
+// __info.routerStatusParser(valueString)__.
+// Parses a router status entry as, described in
+// https://gitweb.torproject.org/torspec.git/tree/dir-spec.txt
+// (search for "router status entry")
+info.routerStatusParser = function(valueString) {
+ let lines = utils.splitLines(valueString),
+ objects = [];
+ for (let line of lines) {
+ // Drop first character and grab data following it.
+ let myData = line.substring(2),
+ // Accumulate more maps with data, depending on the first character in the line.
+ dataFun = {
+ r: data =>
+ utils.listMapData(data, [
+ "nickname",
+ "identity",
+ "digest",
+ "publicationDate",
+ "publicationTime",
+ "IP",
+ "ORPort",
+ "DirPort",
+ ]),
+ a: data => ({ IPv6: data }),
+ s: data => ({ statusFlags: utils.splitAtSpaces(data) }),
+ v: data => ({ version: data }),
+ w: data => utils.listMapData(data, []),
+ p: data => ({ portList: data.split(",") }),
+ }[line.charAt(0)];
+ if (dataFun !== undefined) {
+ objects.push(dataFun(myData));
+ }
+ }
+ return utils.mergeObjects(objects);
+};
+
+// __info.circuitStatusParser(line)__.
+// Parse the output of a circuit status line.
+info.circuitStatusParser = function(line) {
+ let data = utils.listMapData(line, ["id", "status", "circuit"]),
+ circuit = data.circuit;
+ // Parse out the individual circuit IDs and names.
+ if (circuit) {
+ data.circuit = circuit.split(",").map(function(x) {
+ return x.split(/~|=/);
+ });
+ }
+ return data;
+};
+
+// __info.streamStatusParser(line)__.
+// Parse the output of a stream status line.
+info.streamStatusParser = function(text) {
+ return utils.listMapData(text, [
+ "StreamID",
+ "StreamStatus",
+ "CircuitID",
+ "Target",
+ ]);
+};
+
+// TODO: fix this parsing logic to handle bridgeLine correctly
+// fingerprint/id is an optional parameter
+// __info.bridgeParser(bridgeLine)__.
+// Takes a single line from a `getconf bridge` result and returns
+// a map containing the bridge's type, address, and ID.
+info.bridgeParser = function(bridgeLine) {
+ let result = {},
+ tokens = bridgeLine.split(/\s+/);
+ // First check if we have a "vanilla" bridge:
+ if (tokens[0].match(/^\d+\.\d+\.\d+\.\d+/)) {
+ result.type = "vanilla";
+ [result.address, result.ID] = tokens;
+ // Several bridge types have a similar format:
+ } else {
+ result.type = tokens[0];
+ if (
+ [
+ "flashproxy",
+ "fte",
+ "meek",
+ "meek_lite",
+ "obfs3",
+ "obfs4",
+ "scramblesuit",
+ "snowflake",
+ ].includes(result.type)
+ ) {
+ [result.address, result.ID] = tokens.slice(1);
+ }
+ }
+ return result.type ? result : null;
+};
+
+// __info.parsers__.
+// A map of GETINFO and GETCONF keys to parsing function, which convert
+// result strings to JavaScript data.
+info.parsers = {
+ "ns/id/": info.routerStatusParser,
+ "ip-to-country/": utils.identity,
+ "circuit-status": info.applyPerLine(info.circuitStatusParser),
+ bridge: info.bridgeParser,
+ // Currently unused parsers:
+ // "ns/name/" : info.routerStatusParser,
+ // "stream-status" : info.applyPerLine(info.streamStatusParser),
+ // "version" : utils.identity,
+ // "config-file" : utils.identity,
+};
+
+// __info.getParser(key)__.
+// Takes a key and determines the parser function that should be used to
+// convert its corresponding valueString to JavaScript data.
+info.getParser = function(key) {
+ return (
+ info.parsers[key] ||
+ info.parsers[key.substring(0, key.lastIndexOf("/") + 1)]
+ );
+};
+
+// __info.stringToValue(string)__.
+// Converts a key-value string as from GETINFO or GETCONF to a value.
+info.stringToValue = function(string) {
+ // key should look something like `250+circuit-status=` or `250-circuit-status=...`
+ // or `250 circuit-status=...`
+ let matchForKey = string.match(/^250[ +-](.+?)=/),
+ key = matchForKey ? matchForKey[1] : null;
+ if (key === null) {
+ return null;
+ }
+ // matchResult finds a single-line result for `250-` or `250 `,
+ // or a multi-line one for `250+`.
+ let matchResult =
+ string.match(/^250[ -].+?=(.*)$/) ||
+ string.match(/^250\+.+?=([\s\S]*?)^\.$/m),
+ // Retrieve the captured group (the text of the value in the key-value pair)
+ valueString = matchResult ? matchResult[1] : null,
+ // Get the parser function for the key found.
+ parse = info.getParser(key.toLowerCase());
+ if (parse === undefined) {
+ throw new Error("No parser found for '" + key + "'");
+ }
+ // Return value produced by the parser.
+ return parse(valueString);
+};
+
+// __info.getMultipleResponseValues(message)__.
+// Process multiple responses to a GETINFO or GETCONF request.
+info.getMultipleResponseValues = function(message) {
+ return info
+ .keyValueStringsFromMessage(message)
+ .map(info.stringToValue)
+ .filter(utils.identity);
+};
+
+// __info.getInfo(controlSocket, key)__.
+// Sends GETINFO for a single key. Returns a promise with the result.
+info.getInfo = function(aControlSocket, key) {
+ if (!utils.isString(key)) {
+ return utils.rejectPromise("key argument should be a string");
+ }
+ return aControlSocket
+ .sendCommand("getinfo " + key)
+ .then(response => info.getMultipleResponseValues(response)[0]);
+};
+
+// __info.getConf(aControlSocket, key)__.
+// Sends GETCONF for a single key. Returns a promise with the result.
+info.getConf = function(aControlSocket, key) {
+ // GETCONF with a single argument returns results with
+ // one or more lines that look like `250[- ]key=value`.
+ // Any GETCONF lines that contain a single keyword only are currently dropped.
+ // So we can use similar parsing to that for getInfo.
+ if (!utils.isString(key)) {
+ return utils.rejectPromise("key argument should be a string");
+ }
+ return aControlSocket
+ .sendCommand("getconf " + key)
+ .then(info.getMultipleResponseValues);
+};
+
+// ## onionAuth
+// A namespace for functions related to tor's ONION_CLIENT_AUTH_* commands.
+let onionAuth = {};
+
+onionAuth.keyInfoStringsFromMessage = utils.extractor(/^250-CLIENT\s+(.+)$/gim);
+
+onionAuth.keyInfoObjectsFromMessage = function(message) {
+ let keyInfoStrings = onionAuth.keyInfoStringsFromMessage(message);
+ return keyInfoStrings.map(infoStr =>
+ utils.listMapData(infoStr, ["hsAddress", "typeAndKey"])
+ );
+};
+
+// __onionAuth.viewKeys()__.
+// Sends a ONION_CLIENT_AUTH_VIEW command to retrieve the list of private keys.
+// Returns a promise that is fulfilled with an array of key info objects which
+// contain the following properties:
+// hsAddress
+// typeAndKey
+// Flags (e.g., "Permanent")
+onionAuth.viewKeys = function(aControlSocket) {
+ let cmd = "onion_client_auth_view";
+ return aControlSocket
+ .sendCommand(cmd)
+ .then(onionAuth.keyInfoObjectsFromMessage);
+};
+
+// __onionAuth.add(controlSocket, hsAddress, b64PrivateKey, isPermanent)__.
+// Sends a ONION_CLIENT_AUTH_ADD command to add a private key to the
+// Tor configuration.
+onionAuth.add = function(
+ aControlSocket,
+ hsAddress,
+ b64PrivateKey,
+ isPermanent
+) {
+ if (!utils.isString(hsAddress)) {
+ return utils.rejectPromise("hsAddress argument should be a string");
+ }
+
+ if (!utils.isString(b64PrivateKey)) {
+ return utils.rejectPromise("b64PrivateKey argument should be a string");
+ }
+
+ const keyType = "x25519";
+ let cmd = `onion_client_auth_add ${hsAddress} ${keyType}:${b64PrivateKey}`;
+ if (isPermanent) {
+ cmd += " Flags=Permanent";
+ }
+ return aControlSocket.sendCommand(cmd);
+};
+
+// __onionAuth.remove(controlSocket, hsAddress)__.
+// Sends a ONION_CLIENT_AUTH_REMOVE command to remove a private key from the
+// Tor configuration.
+onionAuth.remove = function(aControlSocket, hsAddress) {
+ if (!utils.isString(hsAddress)) {
+ return utils.rejectPromise("hsAddress argument should be a string");
+ }
+
+ let cmd = `onion_client_auth_remove ${hsAddress}`;
+ return aControlSocket.sendCommand(cmd);
+};
+
+// ## event
+// Handlers for events
+
+let event = {};
+
+// __event.parsers__.
+// A map of EVENT keys to parsing functions, which convert result strings to JavaScript
+// data.
+event.parsers = {
+ stream: info.streamStatusParser,
+ // Currently unused:
+ // "circ" : info.circuitStatusParser,
+};
+
+// __event.messageToData(type, message)__.
+// Extract the data from an event. Note, at present
+// we only extract streams that look like `"650" SP...`
+event.messageToData = function(type, message) {
+ let dataText = message.match(/^650 \S+?\s(.*)/m)[1];
+ return dataText && type.toLowerCase() in event.parsers
+ ? event.parsers[type.toLowerCase()](dataText)
+ : null;
+};
+
+// __event.watchEvent(controlSocket, type, filter, onData)__.
+// Watches for a particular type of event. If filter(data) returns true, the event's
+// data is passed to the onData callback. Returns a zero arg function that
+// stops watching the event. Note: we only observe `"650" SP...` events
+// currently (no `650+...` or `650-...` events).
+event.watchEvent = function(controlSocket, type, filter, onData, raw = false) {
+ return controlSocket.addNotificationCallback(
+ new RegExp("^650 " + type),
+ function(message) {
+ let data = event.messageToData(type, message);
+ if (filter === null || filter(data)) {
+ if (raw || !data) {
+ onData(message);
+ return;
+ }
+ onData(data);
+ }
+ }
+ );
+};
+
+// ## tor
+// Things related to the main controller.
+let tor = {};
+
+// __tor.controllerCache__.
+// A map from "unix:socketpath" or "host:port" to controller objects. Prevents
+// redundant instantiation of control sockets.
+tor.controllerCache = new Map();
+
+// __tor.controller(ipcFile, host, port, password)__.
+// Creates a tor controller at the given ipcFile or host and port, with the
+// given password.
+tor.controller = async function(ipcFile, host, port, password) {
+ let socket = await io.controlSocket(ipcFile, host, port, password);
+ return {
+ getInfo: key => info.getInfo(socket, key),
+ getConf: key => info.getConf(socket, key),
+ onionAuthViewKeys: () => onionAuth.viewKeys(socket),
+ onionAuthAdd: (hsAddress, b64PrivateKey, isPermanent) =>
+ onionAuth.add(socket, hsAddress, b64PrivateKey, isPermanent),
+ onionAuthRemove: hsAddress => onionAuth.remove(socket, hsAddress),
+ watchEvent: (type, filter, onData, raw = false) =>
+ event.watchEvent(socket, type, filter, onData, raw),
+ isOpen: () => socket.isOpen(),
+ close: () => {
+ socket.close();
+ },
+ sendCommand: cmd => socket.sendCommand(cmd),
+ };
+};
+
+// ## Export
+
+let controlPortInfo = {};
+
+// __configureControlPortModule(ipcFile, host, port, password)__.
+// Sets Tor control port connection parameters to be used in future calls to
+// the controller() function. Example:
+// configureControlPortModule(undefined, "127.0.0.1", 9151, "MyPassw0rd");
+var configureControlPortModule = function(ipcFile, host, port, password) {
+ controlPortInfo.ipcFile = ipcFile;
+ controlPortInfo.host = host;
+ controlPortInfo.port = port || 9151;
+ controlPortInfo.password = password;
+};
+
+// __controller(avoidCache)__.
+// Instantiates and returns a controller object that is connected and
+// authenticated to a Tor ControlPort using the connection parameters
+// provided in the most recent call to configureControlPortModule(), if
+// the controller doesn't yet exist. Otherwise returns the existing
+// controller to the given ipcFile or host:port. Throws on error.
+//
+// Example:
+//
+// // Get a new controller
+// const avoidCache = true;
+// let c = controller(avoidCache);
+// // Send command and receive `250` reply or error message in a promise:
+// let replyPromise = c.getInfo("ip-to-country/16.16.16.16");
+// // Close the controller permanently
+// c.close();
+var controller = async function(avoidCache) {
+ if (!controlPortInfo.ipcFile && !controlPortInfo.host) {
+ throw new Error("Please call configureControlPortModule first");
+ }
+
+ const dest = controlPortInfo.ipcFile
+ ? `unix:${controlPortInfo.ipcFile.path}`
+ : `${controlPortInfo.host}:${controlPortInfo.port}`;
+
+ // constructor shorthand
+ const newTorController = async () => {
+ return tor.controller(
+ controlPortInfo.ipcFile,
+ controlPortInfo.host,
+ controlPortInfo.port,
+ controlPortInfo.password
+ );
+ };
+
+ // avoid cache so always return a new controller
+ if (avoidCache) {
+ return newTorController();
+ }
+
+ // first check our cache and see if we already have one
+ let cachedController = tor.controllerCache.get(dest);
+ if (cachedController && cachedController.isOpen()) {
+ return cachedController;
+ }
+
+ // create a new one and store in the map
+ cachedController = await newTorController();
+ // overwrite the close() function to prevent consumers from closing a shared/cached controller
+ cachedController.close = () => {
+ throw new Error("May not close cached Tor Controller as it may be in use");
+ };
+
+ tor.controllerCache.set(dest, cachedController);
+ return cachedController;
+};
+
+// __wait_for_controller(avoidCache)
+// Same as controller() function, but explicitly waits until there is a tor daemon
+// to connect to (either launched by tor-launcher, or if we have an existing system
+// tor daemon)
+var wait_for_controller = function(avoidCache) {
+ // if tor process is running (either ours or system) immediately return controller
+ if (!TorMonitorService.ownsTorDaemon || TorMonitorService.isRunning) {
+ return controller(avoidCache);
+ }
+
+ // otherwise we must wait for tor to finish launching before resolving
+ return new Promise((resolve, reject) => {
+ let observer = {
+ observe: async (subject, topic, data) => {
+ if (topic === TorTopics.ProcessIsReady) {
+ try {
+ resolve(await controller(avoidCache));
+ } catch (err) {
+ reject(err);
+ }
+ Services.obs.removeObserver(observer, TorTopics.ProcessIsReady);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, TorTopics.ProcessIsReady);
+ });
+};
+
+// Export functions for external use.
+var EXPORTED_SYMBOLS = [
+ "configureControlPortModule",
+ "controller",
+ "wait_for_controller",
+];
diff --git a/toolkit/torbutton/modules/utils.js b/toolkit/torbutton/modules/utils.js
new file mode 100644
index 000000000000..30947a737cb9
--- /dev/null
+++ b/toolkit/torbutton/modules/utils.js
@@ -0,0 +1,318 @@
+// # Utils.js
+// Various helpful utility functions.
+
+// ### Import Mozilla Services
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// ## Pref utils
+
+// __prefs__. A shortcut to Mozilla Services.prefs.
+let prefs = Services.prefs;
+
+// __getPrefValue(prefName)__
+// Returns the current value of a preference, regardless of its type.
+var getPrefValue = function(prefName) {
+ switch (prefs.getPrefType(prefName)) {
+ case prefs.PREF_BOOL:
+ return prefs.getBoolPref(prefName);
+ case prefs.PREF_INT:
+ return prefs.getIntPref(prefName);
+ case prefs.PREF_STRING:
+ return prefs.getCharPref(prefName);
+ default:
+ return null;
+ }
+};
+
+// __bindPref(prefName, prefHandler, init)__
+// Applies prefHandler whenever the value of the pref changes.
+// If init is true, applies prefHandler to the current value.
+// Returns a zero-arg function that unbinds the pref.
+var bindPref = function(prefName, prefHandler, init = false) {
+ let update = () => {
+ prefHandler(getPrefValue(prefName));
+ },
+ observer = {
+ observe(subject, topic, data) {
+ if (data === prefName) {
+ update();
+ }
+ },
+ };
+ prefs.addObserver(prefName, observer);
+ if (init) {
+ update();
+ }
+ return () => {
+ prefs.removeObserver(prefName, observer);
+ };
+};
+
+// __bindPrefAndInit(prefName, prefHandler)__
+// Applies prefHandler to the current value of pref specified by prefName.
+// Re-applies prefHandler whenever the value of the pref changes.
+// Returns a zero-arg function that unbinds the pref.
+var bindPrefAndInit = (prefName, prefHandler) =>
+ bindPref(prefName, prefHandler, true);
+
+// ## Observers
+
+// __observe(topic, callback)__.
+// Observe the given topic. When notification of that topic
+// occurs, calls callback(subject, data). Returns a zero-arg
+// function that stops observing.
+var observe = function(topic, callback) {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (topic === aTopic) {
+ callback(aSubject, aData);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ return () => Services.obs.removeObserver(observer, topic);
+};
+
+// ## Environment variables
+
+// __env__.
+// Provides access to process environment variables.
+let env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+);
+
+// __getEnv(name)__.
+// Reads the environment variable of the given name.
+var getEnv = function(name) {
+ return env.exists(name) ? env.get(name) : undefined;
+};
+
+// __getLocale
+// Returns the app locale to be used in tor-related urls.
+var getLocale = function() {
+ const locale = Services.locale.appLocaleAsBCP47;
+ if (locale === "ja-JP-macos") {
+ // We don't want to distinguish the mac locale.
+ return "ja";
+ }
+ return locale;
+};
+
+// ## Windows
+
+// __dialogsByName__.
+// Map of window names to dialogs.
+let dialogsByName = {};
+
+// __showDialog(parent, url, name, features, arg1, arg2, ...)__.
+// Like window.openDialog, but if the window is already
+// open, just focuses it instead of opening a new one.
+var showDialog = function(parent, url, name, features) {
+ let existingDialog = dialogsByName[name];
+ if (existingDialog && !existingDialog.closed) {
+ existingDialog.focus();
+ return existingDialog;
+ }
+ let newDialog = parent.openDialog.apply(parent, Array.slice(arguments, 1));
+ dialogsByName[name] = newDialog;
+ return newDialog;
+};
+
+// ## Tor control protocol utility functions
+
+let _torControl = {
+ // Unescape Tor Control string aStr (removing surrounding "" and \ escapes).
+ // Based on Vidalia's src/common/stringutil.cpp:string_unescape().
+ // Returns the unescaped string. Throws upon failure.
+ // Within Tor Launcher, the file components/tl-protocol.js also contains a
+ // copy of _strUnescape().
+ _strUnescape(aStr) {
+ if (!aStr) {
+ return aStr;
+ }
+
+ var len = aStr.length;
+ if (len < 2 || '"' != aStr.charAt(0) || '"' != aStr.charAt(len - 1)) {
+ return aStr;
+ }
+
+ const kHexRE = /[0-9A-Fa-f]{2}/;
+ const kOctalRE = /[0-7]{3}/;
+ var rv = "";
+ var i = 1;
+ var lastCharIndex = len - 2;
+ while (i <= lastCharIndex) {
+ var c = aStr.charAt(i);
+ if ("\\" == c) {
+ if (++i > lastCharIndex) {
+ throw new Error("missing character after \\");
+ }
+
+ c = aStr.charAt(i);
+ if ("n" == c) {
+ rv += "\n";
+ } else if ("r" == c) {
+ rv += "\r";
+ } else if ("t" == c) {
+ rv += "\t";
+ } else if ("x" == c) {
+ if (i + 2 > lastCharIndex) {
+ throw new Error("not enough hex characters");
+ }
+
+ let s = aStr.substr(i + 1, 2);
+ if (!kHexRE.test(s)) {
+ throw new Error("invalid hex characters");
+ }
+
+ let val = parseInt(s, 16);
+ rv += String.fromCharCode(val);
+ i += 3;
+ } else if (this._isDigit(c)) {
+ let s = aStr.substr(i, 3);
+ if (i + 2 > lastCharIndex) {
+ throw new Error("not enough octal characters");
+ }
+
+ if (!kOctalRE.test(s)) {
+ throw new Error("invalid octal characters");
+ }
+
+ let val = parseInt(s, 8);
+ rv += String.fromCharCode(val);
+ i += 3;
+ } // "\\" and others
+ else {
+ rv += c;
+ ++i;
+ }
+ } else if ('"' == c) {
+ throw new Error('unescaped " within string');
+ } else {
+ rv += c;
+ ++i;
+ }
+ }
+
+ // Convert from UTF-8 to Unicode. TODO: is UTF-8 always used in protocol?
+ return decodeURIComponent(escape(rv));
+ }, // _strUnescape()
+
+ // Within Tor Launcher, the file components/tl-protocol.js also contains a
+ // copy of _isDigit().
+ _isDigit(aChar) {
+ const kRE = /^\d$/;
+ return aChar && kRE.test(aChar);
+ },
+}; // _torControl
+
+// __unescapeTorString(str, resultObj)__.
+// Unescape Tor Control string str (removing surrounding "" and \ escapes).
+// Returns the unescaped string. Throws upon failure.
+var unescapeTorString = function(str) {
+ return _torControl._strUnescape(str);
+};
+
+var getFPDFromHost = hostname => {
+ try {
+ return Services.eTLD.getBaseDomainFromHost(hostname);
+ } catch (e) {
+ if (
+ e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
+ e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
+ ) {
+ return hostname;
+ }
+ }
+ return null;
+};
+
+// Assuming this is called with gBrowser.selectedBrowser
+var getDomainForBrowser = browser => {
+ let fpd = browser.contentPrincipal.originAttributes.firstPartyDomain;
+ // Bug 31562: For neterror or certerror, get the original URL from
+ // browser.currentURI and use it to calculate the firstPartyDomain.
+ let knownErrors = ["about:neterror", "about:certerror"];
+ let documentURI = browser.documentURI;
+ if (
+ documentURI &&
+ documentURI.schemeIs("about") &&
+ knownErrors.some(x => documentURI.spec.startsWith(x))
+ ) {
+ let knownSchemes = ["http", "https", "ftp"];
+ let currentURI = browser.currentURI;
+ if (currentURI && knownSchemes.some(x => currentURI.schemeIs(x))) {
+ fpd = getFPDFromHost(currentURI.host) || fpd;
+ }
+ }
+ return fpd;
+};
+
+var m_tb_torlog = Cc["@torproject.org/torbutton-logger;1"].getService(
+ Ci.nsISupports
+).wrappedJSObject;
+
+var m_tb_string_bundle = torbutton_get_stringbundle();
+
+function torbutton_safelog(nLevel, sMsg, scrub) {
+ m_tb_torlog.safe_log(nLevel, sMsg, scrub);
+ return true;
+}
+
+function torbutton_log(nLevel, sMsg) {
+ m_tb_torlog.log(nLevel, sMsg);
+
+ // So we can use it in boolean expressions to determine where the
+ // short-circuit is..
+ return true;
+}
+
+// load localization strings
+function torbutton_get_stringbundle() {
+ var o_stringbundle = false;
+
+ try {
+ var oBundle = Services.strings;
+ o_stringbundle = oBundle.createBundle(
+ "chrome://torbutton/locale/torbutton.properties"
+ );
+ } catch (err) {
+ o_stringbundle = false;
+ }
+ if (!o_stringbundle) {
+ torbutton_log(5, "ERROR (init): failed to find torbutton-bundle");
+ }
+
+ return o_stringbundle;
+}
+
+function torbutton_get_property_string(propertyname) {
+ try {
+ if (!m_tb_string_bundle) {
+ m_tb_string_bundle = torbutton_get_stringbundle();
+ }
+
+ return m_tb_string_bundle.GetStringFromName(propertyname);
+ } catch (e) {
+ torbutton_log(4, "Unlocalized string " + propertyname);
+ }
+
+ return propertyname;
+}
+
+// Export utility functions for external use.
+let EXPORTED_SYMBOLS = [
+ "bindPref",
+ "bindPrefAndInit",
+ "getEnv",
+ "getLocale",
+ "getDomainForBrowser",
+ "getPrefValue",
+ "observe",
+ "showDialog",
+ "show_torbrowser_manual",
+ "unescapeTorString",
+ "torbutton_safelog",
+ "torbutton_log",
+ "torbutton_get_property_string",
+];
diff --git a/toolkit/torbutton/moz.build b/toolkit/torbutton/moz.build
new file mode 100644
index 000000000000..376c99765de4
--- /dev/null
+++ b/toolkit/torbutton/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+JAR_MANIFESTS += ['jar.mn']
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
index c24943e9dee3..de0091ae8d4d 100644
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
@@ -83,7 +83,11 @@ function getGlobalScriptIncludes(scriptPath) {
"browser/components/screenshots/content/"
)
.replace("chrome://browser/content/", "browser/base/content/")
- .replace("chrome://global/content/", "toolkit/content/");
+ .replace("chrome://global/content/", "toolkit/content/")
+ .replace(
+ "chrome://torbutton/content/",
+ "toolkit/torproject/torbutton/chrome/content/"
+ );
for (let mapping of Object.getOwnPropertyNames(MAPPINGS)) {
if (sourceFile.includes(mapping)) {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the tbb-commits
mailing list