[tor-commits] [torbutton/master] Bug #13672. bind tor circuit display to a pref
gk at torproject.org
gk at torproject.org
Tue Dec 2 13:00:24 UTC 2014
commit bdc987b01c977c177d567eb850ce4c98d02e3538
Author: Arthur Edelstein <arthuredelstein at gmail.com>
Date: Wed Nov 5 23:21:59 2014 -0800
Bug #13672. bind tor circuit display to a pref
---
src/chrome/content/tor-circuit-display.js | 256 +++++++++++++++++++----------
src/chrome/content/torbutton.js | 3 +-
src/defaults/preferences/preferences.js | 1 +
src/modules/tor-control-port.js | 33 ++--
4 files changed, 188 insertions(+), 105 deletions(-)
diff --git a/src/chrome/content/tor-circuit-display.js b/src/chrome/content/tor-circuit-display.js
index 49cd832..0817aa6 100644
--- a/src/chrome/content/tor-circuit-display.js
+++ b/src/chrome/content/tor-circuit-display.js
@@ -15,9 +15,12 @@
/* global document, gBrowser, Components */
// ### Main function
-// __runTorCircuitDisplay(host, port, password)__.
-// The single function we run to activate automatic display of the Tor circuit..
-let runTorCircuitDisplay = (function () {
+// __createTorCircuitDisplay(host, port, password, enablePrefName)__.
+// The single function that prepares tor circuit display. Connects to a tor
+// control port with the given host, port, and password, and binds to
+// a named bool pref whose value determines whether the circuit display
+// is enabled or disabled.
+let createTorCircuitDisplay = (function () {
"use strict";
@@ -32,28 +35,12 @@ let { controller } = Cu.import("resource://torbutton/modules/tor-control-port.js
let logger = Cc["@torproject.org/torbutton-logger;1"]
.getService(Components.interfaces.nsISupports).wrappedJSObject;
-// __regionBundle__.
-// A list of localized region (country) names.
-let regionBundle = Services.strings.createBundle(
- "chrome://global/locale/regionNames.properties");
-
-// __localizedCountryNameFromCode(countryCode)__.
-// Convert a country code to a localized country name.
-// Example: `'de'` -> `'Deutschland'` in German locale.
-let localizedCountryNameFromCode = function (countryCode) {
- if (typeof(countryCode) === "undefined") return "";
- try {
- return regionBundle.GetStringFromName(countryCode.toLowerCase());
- } catch (e) {
- return countryCode.toUpperCase();
- }
-};
+// ## Circuit/stream domain and node monitoring
-// __domainToNodeDataMap__.
// A mutable map that stores the current nodes for each domain.
-let domainToNodeDataMap = {};
-
-let knownCircuitIDs = {};
+let domainToNodeDataMap = {},
+ // A mutable map that records what circuits are already known.
+ knownCircuitIDs = {};
// __trimQuotes(s)__.
// Removes quotation marks around a quoted string.
@@ -86,6 +73,70 @@ let nodeDataForCircuit = function (controller, circuitEvent, onResult) {
nodeDataForID(controller, ids, onResult);
};
+// __getCircuitStatusByID(aController, circuitID, onCircuitStatus)__
+// Returns the circuit status for the circuit with the given ID
+// via onCircuitStatus(status).
+let getCircuitStatusByID = function(aController, circuitID, onCircuitStatus) {
+ aController.getInfo("circuit-status", function (circuitStatuses) {
+ for (let circuitStatus of circuitStatuses) {
+ if (circuitStatus.id === circuitID) {
+ onCircuitStatus(circuitStatus);
+ }
+ }
+ });
+};
+
+// __collectIsolationData(aController)__.
+// 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 domainToNodeDataMap.
+let collectIsolationData = function (aController) {
+ aController.watchEvent(
+ "STREAM",
+ streamEvent => streamEvent.StreamStatus === "SENTCONNECT",
+ function (streamEvent) {
+ if (!knownCircuitIDs[streamEvent.CircuitID]) {
+ logger.eclog(3, "streamEvent.CircuitID: " + streamEvent.CircuitID);
+ knownCircuitIDs[streamEvent.CircuitID] = true;
+ getCircuitStatusByID(aController, streamEvent.CircuitID, function (circuitStatus) {
+ let domain = trimQuotes(circuitStatus.SOCKS_USERNAME);
+ if (domain) {
+ nodeDataForCircuit(aController, circuitStatus, function (nodeData) {
+ domainToNodeDataMap[domain] = nodeData;
+ });
+ }
+ });
+ }
+ });
+};
+
+// ## User interface
+
+// __regionBundle__.
+// A list of localized region (country) names.
+let regionBundle = Services.strings.createBundle(
+ "chrome://global/locale/regionNames.properties");
+
+// __localizedCountryNameFromCode(countryCode)__.
+// Convert a country code to a localized country name.
+// Example: `'de'` -> `'Deutschland'` in German locale.
+let localizedCountryNameFromCode = function (countryCode) {
+ if (typeof(countryCode) === "undefined") return "";
+ try {
+ return regionBundle.GetStringFromName(countryCode.toLowerCase());
+ } catch (e) {
+ return countryCode.toUpperCase();
+ }
+};
+
+// __showCircuitDisplay(show)__.
+// If show === true, makes the circuit display visible.
+let showCircuitDisplay = function (show) {
+ document.querySelector("svg#tor-circuit").style.display = show ?
+ 'block' : 'none';
+};
+
// __nodeLines(nodeData)__.
// Takes a nodeData array of three items each like
// `{ ip : "12.34.56.78", country : "fr" }`
@@ -121,7 +172,6 @@ let updateCircuitDisplay = function () {
document.querySelector("svg#tor-circuit text#domain").innerHTML = "(" + domain + "):";
// Update the displayed information for the relay nodes.
let diagramNodes = document.querySelectorAll("svg#tor-circuit text.node-text"),
- //diagramCircles = document.querySelectorAll("svg#tor-circuit .node-circule"),
lines = nodeLines(nodeData);
for (let i = 0; i < diagramNodes.length; ++i) {
let line = lines[i];
@@ -130,85 +180,109 @@ let updateCircuitDisplay = function () {
}
}
// Only show the Tor circuit if we have a domain and node data.
- document.querySelector("svg#tor-circuit").style.display = (domain && nodeData) ?
- 'block' : 'none';
+ showCircuitDisplay(domain && nodeData);
}
};
-// __getCircuitStatusByID(aController, circuitID, onCircuitStatus)__
-// Returns the circuit status for the circuit with the given ID
-// via onCircuitStatus(status).
-let getCircuitStatusByID = function(aController, circuitID, onCircuitStatus) {
- aController.getInfo("circuit-status", function (circuitStatuses) {
- for (let circuitStatus of circuitStatuses) {
- if (circuitStatus.id === circuitID) {
- onCircuitStatus(circuitStatus);
- }
- }
- });
-};
-
-// __collectIsolationData(aController)__.
-// Watches for STREAM SENTCONNECT events. When a SENTCONNECT event occurs, then
-// the isolation settings (SOCKS username+password) become 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 domainToNodeDataMap.
-let collectIsolationData = function (aController) {
- aController.watchEvent(
- "STREAM",
- streamEvent => streamEvent.StreamStatus === "SENTCONNECT",
- function (streamEvent) {
- if (!knownCircuitIDs[streamEvent.CircuitID]) {
- logger.eclog(4, "streamEvent.CircuitID: " + streamEvent.CircuitID);
- knownCircuitIDs[streamEvent.CircuitID] = true;
- getCircuitStatusByID(aController, streamEvent.CircuitID, function (circuitStatus) {
- let domain = trimQuotes(circuitStatus.SOCKS_USERNAME);
- if (domain) {
- nodeDataForCircuit(aController, circuitStatus, function (nodeData) {
- domainToNodeDataMap[domain] = nodeData;
- updateCircuitDisplay();
- });
- } else {
- updateCircuitDisplay();
- }
- });
- }
- });
-};
-
-// __syncDisplayWithSelectedTab()__.
+// __syncDisplayWithSelectedTab(syncOn)__.
// We may have multiple tabs, but there is only one instance of TorButton's popup
// panel for displaying the Tor circuit UI. Therefore we need to update the display
// to show the currently selected tab at its current location.
-let syncDisplayWithSelectedTab = function () {
- // Whenever a different tab is selected, change the circuit display
- // to show the circuit for that tab's domain.
- gBrowser.tabContainer.addEventListener("TabSelect", function (event) {
- updateCircuitDisplay();
- });
- // If the currently selected tab has been sent to a new location,
- // update the circuit to reflect that.
- gBrowser.addTabsProgressListener({ onLocationChange : function (aBrowser) {
- if (aBrowser == gBrowser.selectedBrowser) {
+let syncDisplayWithSelectedTab = (function() {
+ let listener1 = event => { updateCircuitDisplay(); },
+ listener2 = { onLocationChange : function (aBrowser) {
+ if (aBrowser === gBrowser.selectedBrowser) {
+ updateCircuitDisplay();
+ }
+ } };
+ return function (syncOn) {
+ if (syncOn) {
+ // Whenever a different tab is selected, change the circuit display
+ // to show the circuit for that tab's domain.
+ gBrowser.tabContainer.addEventListener("TabSelect", listener1);
+ // If the currently selected tab has been sent to a new location,
+ // update the circuit to reflect that.
+ gBrowser.addTabsProgressListener(listener2);
+ // Get started with a correct display.
updateCircuitDisplay();
+ } else {
+ // Stop syncing.
+ gBrowser.tabContainer.removeEventListener("TabSelect", listener1);
+ gBrowser.removeTabsProgressListener(listener2);
+ // Hide the display.
+ showCircuitDisplay(false);
}
- } });
+ };
+})();
+
+// ## Pref utils
- // Get started with a correct display.
- updateCircuitDisplay();
+// __prefs__. A shortcut to Mozilla Services.prefs.
+let prefs = Services.prefs;
+
+// __getPrefValue(prefName)__
+// Returns the current value of a preference, regardless of its type.
+let 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;
+ }
};
-// __display(host, port, password)__.
-// The main function for activating automatic display of the Tor circuit.
-// A reference to this function (called runTorCircuitDisplay) is exported as a global.
-let display = function (host, port, password) {
- let myController = controller(host, port || 9151, password, function (x) { logger.eclog(5, x); });
- syncDisplayWithSelectedTab();
- collectIsolationData(myController);
+// __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.
+let bindPrefAndInit = function (prefName, prefHandler) {
+ let update = () => { prefHandler(getPrefValue(prefName)); },
+ observer = { observe : function (subject, topic, data) {
+ if (data === prefName) {
+ update();
+ }
+ } };
+ prefs.addObserver(prefName, observer, false);
+ update();
+ return () => { prefs.removeObserver(prefName, observer); };
};
-return display;
+// ## Main function
-// Finish runTorCircuitDisplay()
-})();
+// setupDisplay(host, port, password, enablePrefName)__.
+// Returns a function that lets you start/stop automatic display of the Tor circuit.
+// A reference to this function (called createTorCircuitDisplay) is exported as a global.
+let setupDisplay = function (host, port, password, enablePrefName) {
+ let myController = null,
+ stop = function() {
+ if (myController) {
+ syncDisplayWithSelectedTab(false);
+ myController.close();
+ myController = null;
+ }
+ },
+ start = function () {
+ if (!myController) {
+ myController = controller(host, port || 9151, password, function (err) {
+ // An error has occurred.
+ logger.eclog(5, err);
+ logger.eclog(5, "Disabling tor display circuit because of an error.");
+ stop();
+ });
+ syncDisplayWithSelectedTab(true);
+ collectIsolationData(myController);
+ }
+ };
+ 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", unbindPref);
+ } catch (e) {
+ logger.eclog(5, "Error: " + e.message + "\n" + e.stack);
+ }
+};
+
+return setupDisplay;
+// Finish createTorCircuitDisplay()
+})();
diff --git a/src/chrome/content/torbutton.js b/src/chrome/content/torbutton.js
index 2e921da..8dba1fd 100644
--- a/src/chrome/content/torbutton.js
+++ b/src/chrome/content/torbutton.js
@@ -619,7 +619,8 @@ function torbutton_init() {
torbutton_update_statusbar(mode);
torbutton_notify_if_update_needed();
- runTorCircuitDisplay(m_tb_control_host, m_tb_control_port, m_tb_control_pass);
+ createTorCircuitDisplay(m_tb_control_host, m_tb_control_port, m_tb_control_pass,
+ "extensions.torbutton.display_circuit");
torbutton_log(3, 'init completed');
}
diff --git a/src/defaults/preferences/preferences.js b/src/defaults/preferences/preferences.js
index d527d7e..3e02b77 100644
--- a/src/defaults/preferences/preferences.js
+++ b/src/defaults/preferences/preferences.js
@@ -4,6 +4,7 @@ pref("extensions.torbutton.loglevel",4);
pref("extensions.torbutton.logmethod",1); // 0=stdout, 1=errorconsole, 2=debuglog
// Display prefs
+pref("extensions.torbutton.display_circuit", true);
pref("extensions.torbutton.display_panel",true);
pref("extensions.torbutton.panel_style",'text');
pref("extensions.torbutton at torproject.org.description", "chrome://torbutton/locale/torbutton.properties");
diff --git a/src/modules/tor-control-port.js b/src/modules/tor-control-port.js
index ada80b5..eae62f5 100644
--- a/src/modules/tor-control-port.js
+++ b/src/modules/tor-control-port.js
@@ -156,15 +156,16 @@ io.onLineFromOnMessage = function (onMessage) {
// Add to the list of pending lines.
pendingLines.push(line);
// If line is the last in a message, then pass on the full multiline message.
- if (line.match(/^\d\d\d /) && (pendingLines.length == 1 ||
- pendingLines[0].startsWith(line.substring(0,3)))) {
+ if (line.match(/^\d\d\d /) &&
+ (pendingLines.length === 1 ||
+ pendingLines[0].substring(0,3) === line.substring(0,3))) {
// Combine pending lines to form message.
let message = pendingLines.join("\r\n");
+ log("controlPort >> " + message);
// Wipe pendingLines before we call onMessage, in case onMessage throws an error.
pendingLines = [];
// Pass multiline message to onMessage.
onMessage(message);
- log("controlPort >> " + message);
}
};
};
@@ -547,11 +548,17 @@ event.watchEvent = function (controlSocket, type, filter, onData) {
// Things related to the main controller.
let tor = tor || {};
+// __tor.controllerCache__.
+// A map from "host:port" to controller objects. Prevents redundant instantiation
+// of control sockets.
+tor.controllerCache = {};
+
// __tor.controller(host, port, password, onError)__.
// Creates a tor controller at the given host and port, with the given password.
// onError returns asynchronously whenever a connection error occurs.
tor.controller = function (host, port, password, onError) {
- let socket = io.controlSocket(host, port, password, onError);
+ let socket = io.controlSocket(host, port, password, onError),
+ isOpen = true;
return { getInfo : function (key, log) { info.getInfo(socket, key, log); } ,
getInfoMultiple : function (keys, log) {
info.getInfoMultiple(socket, keys, log);
@@ -559,14 +566,11 @@ tor.controller = function (host, port, password, onError) {
watchEvent : function (type, filter, onData) {
event.watchEvent(socket, type, filter, onData);
},
- close : socket.close };
+ isOpen : () => isOpen,
+ close : () => { isOpen = false; socket.close(); }
+ };
};
-// __tor.controllerCache__.
-// A map from "host:port" to controller objects. Prevents redundant instantiation
-// of control sockets.
-tor.controllerCache = {};
-
// ## Export
// __controller(host, port, password, onError)__.
@@ -584,9 +588,12 @@ tor.controllerCache = {};
// // Close the controller permanently
// c.close();
let controller = function (host, port, password, onError) {
- let dest = host + ":" + port;
- return (tor.controllerCache[dest] = tor.controllerCache[dest] ||
- tor.controller(host, port, password, onError));
+ let dest = host + ":" + port,
+ maybeController = tor.controllerCache[dest];
+ return (tor.controllerCache[dest] =
+ (maybeController && maybeController.isOpen()) ?
+ maybeController :
+ tor.controller(host, port, password, onError));
};
// Export the controller function for external use.
More information about the tor-commits
mailing list