[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [tor-browser] 64/85: fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-2
in repository tor-browser.
commit 6bb230b225baf1a79439324a20fb8506d6d899b3
Author: Pier Angelo Vendrame <pierov@xxxxxxxxxxxxxx>
AuthorDate: Fri Apr 8 11:20:37 2022 +0200
fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection
Changes introduced by !275
---
.../torpreferences/content/connectionPane.js | 402 ++++++---------------
.../torpreferences/content/connectionPane.xhtml | 17 +
.../torpreferences/content/torPreferences.css | 130 ++++++-
3 files changed, 251 insertions(+), 298 deletions(-)
diff --git a/browser/components/torpreferences/content/connectionPane.js b/browser/components/torpreferences/content/connectionPane.js
index 309d6498a0c80..46fbbfecf832f 100644
--- a/browser/components/torpreferences/content/connectionPane.js
+++ b/browser/components/torpreferences/content/connectionPane.js
@@ -110,6 +110,7 @@ const gConnectionPane = (function() {
chooseForMe: "#torPreferences-bridges-buttonChooseBridgeForMe",
currentHeader: "#torPreferences-currentBridges-header",
currentHeaderText: "#torPreferences-currentBridges-headerText",
+ currentDescriptionText: "#torPreferences-currentBridges-description",
switch: "#torPreferences-currentBridges-switch",
cards: "#torPreferences-currentBridges-cards",
cardTemplate: "#torPreferences-bridgeCard-template",
@@ -135,6 +136,13 @@ const gConnectionPane = (function() {
requestButton: "#torPreferences-addBridge-buttonRequestBridge",
enterLabel: "#torPreferences-addBridge-labelEnterBridge",
enterButton: "#torPreferences-addBridge-buttonEnterBridge",
+ removeOverlay: "#bridge-remove-overlay",
+ removeModal: "#bridge-remove-modal",
+ removeDismiss: "#bridge-remove-dismiss",
+ removeQuestion: "#bridge-remove-question",
+ removeWarning: "#bridge-remove-warning",
+ removeConfirm: "#bridge-remove-confirm",
+ removeCancel: "#bridge-remove-cancel",
},
advanced: {
header: "h1#torPreferences-advanced-header",
@@ -280,9 +288,7 @@ const gConnectionPane = (function() {
if (TorConnect.state === TorConnectState.Bootstrapped) {
torIcon.className = "connected";
torStatus.textContent = TorStrings.settings.statusTorConnected;
- } else if (
- TorConnect.detectedCensorshipLevel > TorCensorshipLevel.None
- ) {
+ } else if (TorConnect.hasBootstrapEverFailed) {
torIcon.className = "blocked";
torStatus.textContent = TorStrings.settings.statusTorBlocked;
} else {
@@ -346,36 +352,46 @@ const gConnectionPane = (function() {
TorConnect.beginAutoBootstrap(location.value);
});
this._populateLocations = () => {
- let value = location.value;
+ const currentValue = location.value;
locationEntries.textContent = "";
-
- {
- const item = document.createXULElement("menuitem");
- item.setAttribute("value", "");
- item.setAttribute(
- "label",
- TorStrings.settings.bridgeLocationAutomatic
- );
- locationEntries.appendChild(item);
- }
-
- const codes = TorConnect.countryCodes;
- const items = codes.map(code => {
+ const createItem = (value, label, disabled) => {
const item = document.createXULElement("menuitem");
- item.setAttribute("value", code);
- item.setAttribute(
- "label",
- TorConnect.countryNames[code]
- ? TorConnect.countryNames[code]
- : code
- );
+ item.setAttribute("value", value);
+ item.setAttribute("label", label);
+ if (disabled) {
+ item.setAttribute("disabled", "true");
+ }
return item;
- });
- items.sort((left, right) =>
- left.textContent.localeCompare(right.textContent)
+ };
+ const addLocations = codes => {
+ const items = [];
+ for (const code of codes) {
+ items.push(
+ createItem(
+ code,
+ TorConnect.countryNames[code]
+ ? TorConnect.countryNames[code]
+ : code
+ )
+ );
+ }
+ items.sort((left, right) => left.label.localeCompare(right.label));
+ locationEntries.append(...items);
+ };
+ locationEntries.append(
+ createItem("", TorStrings.settings.bridgeLocationAutomatic)
);
- locationEntries.append(...items);
- location.value = value;
+ if (TorConnect.countryCodes.length) {
+ locationEntries.append(
+ createItem("", TorStrings.settings.bridgeLocationFrequent, true)
+ );
+ addLocations(TorConnect.countryCodes);
+ locationEntries.append(
+ createItem("", TorStrings.settings.bridgeLocationOther, true)
+ );
+ }
+ addLocations(Object.keys(TorConnect.countryNames));
+ location.value = currentValue;
};
this._showAutoconfiguration = () => {
if (
@@ -413,6 +429,9 @@ const gConnectionPane = (function() {
this._populateBridgeCards();
});
});
+ prefpane.querySelector(
+ selectors.bridges.currentDescriptionText
+ ).textContent = TorStrings.settings.bridgeCurrentDescription;
const bridgeTemplate = prefpane.querySelector(
selectors.bridges.cardTemplate
);
@@ -439,6 +458,12 @@ const gConnectionPane = (function() {
card.removeAttribute("id");
const grid = card.querySelector(selectors.bridges.cardQrGrid);
card.addEventListener("click", e => {
+ if (
+ card.classList.contains("currently-connected") ||
+ bridgeCards.classList.contains("single-card")
+ ) {
+ return;
+ }
let target = e.target;
let apply = true;
while (target !== null && target !== card && apply) {
@@ -598,7 +623,7 @@ const gConnectionPane = (function() {
const removeAll = prefpane.querySelector(selectors.bridges.removeAll);
removeAll.setAttribute("label", TorStrings.settings.bridgeRemoveAll);
removeAll.addEventListener("command", () => {
- this.onRemoveAllBridges();
+ this._confirmBridgeRemoval();
});
this._populateBridgeCards = async () => {
const collapseThreshold = 4;
@@ -617,6 +642,7 @@ const gConnectionPane = (function() {
bridgeCards.removeAttribute("hidden");
bridgeSwitch.checked = TorSettings.bridges.enabled;
bridgeCards.classList.toggle("disabled", !TorSettings.bridges.enabled);
+ bridgeCards.classList.toggle("single-card", numBridges === 1);
let shownCards = 0;
const toShow = this._currentBridgesExpanded
@@ -787,7 +813,37 @@ const gConnectionPane = (function() {
});
}
- Services.obs.addObserver(this, TorConnectTopics.StateChange);
+ {
+ const overlay = prefpane.querySelector(selectors.bridges.removeOverlay);
+ this._confirmBridgeRemoval = () => {
+ overlay.classList.remove("hidden");
+ };
+ const closeDialog = () => {
+ overlay.classList.add("hidden");
+ };
+ overlay.addEventListener("click", closeDialog);
+ const modal = prefpane.querySelector(selectors.bridges.removeModal);
+ modal.addEventListener("click", e => {
+ e.stopPropagation();
+ });
+ const dismiss = prefpane.querySelector(selectors.bridges.removeDismiss);
+ dismiss.addEventListener("click", closeDialog);
+ const question = prefpane.querySelector(
+ selectors.bridges.removeQuestion
+ );
+ question.textContent = TorStrings.settings.removeBridgesQuestion;
+ const warning = prefpane.querySelector(selectors.bridges.removeWarning);
+ warning.textContent = TorStrings.settings.removeBridgesWarning;
+ const confirm = prefpane.querySelector(selectors.bridges.removeConfirm);
+ confirm.setAttribute("label", TorStrings.settings.remove);
+ confirm.addEventListener("command", () => {
+ this.onRemoveAllBridges();
+ closeDialog();
+ });
+ const cancel = prefpane.querySelector(selectors.bridges.removeCancel);
+ cancel.setAttribute("label", TorStrings.settings.cancel);
+ cancel.addEventListener("command", closeDialog);
+ }
// Advanced setup
prefpane.querySelector(selectors.advanced.header).innerText =
@@ -816,6 +872,8 @@ const gConnectionPane = (function() {
torLogsButton.addEventListener("command", () => {
this.onViewTorLogs();
});
+
+ Services.obs.addObserver(this, TorConnectTopics.StateChange);
},
init() {
@@ -915,6 +973,9 @@ const gConnectionPane = (function() {
onRemoveAllBridges() {
TorSettings.bridges.enabled = false;
TorSettings.bridges.bridge_strings = "";
+ if (TorSettings.bridges.source == TorBridgeSource.BuiltIn) {
+ TorSettings.bridges.builtin_type = "";
+ }
TorSettings.saveToPrefs();
TorSettings.applySettings().then(result => {
this._populateBridgeCards();
@@ -1018,272 +1079,33 @@ function makeBridgeId(bridgeString) {
// JS uses UTF-16. While most of these emojis are surrogate pairs, a few
// ones fit one UTF-16 character. So we could not use neither indices,
// nor substr, nor some function to split the string.
- const emojis = [
- "😄️",
- "😒️",
- "😉",
- "😭️",
- "😂️",
- "😎️",
- "🤩️",
- "😘",
- "😜️",
- "😏️",
- "😷",
- "🤢",
- "🤕",
- "🤧",
- "🥵",
- "🥶",
- "🥴",
- "😵️",
- "🤮️",
- "🤑",
- "🤔",
- "🫢",
- "🤐",
- "😮💨",
- "😐",
- "🤤",
- "😴",
- "🤯",
- "🤠",
- "🥳",
- "🥸",
- "🤓",
- "🧐",
- "😨",
- "😳",
- "🥺",
- "🤬",
- "😈",
- "👿",
- "💀",
- "💩",
- "🤡",
- "👺",
- "👻",
- "👽",
- "🦴",
- "🤖",
- "😸",
- "🙈",
- "🙉",
- "🙊",
- "💋",
- "💖",
- "💯",
- "💢",
- "💧",
- "💨",
- "💭",
- "💤",
- "👋",
- "👌",
- "✌",
- "👍",
- "👎",
- "🤛",
- "🙌",
- "💪",
- "🙏",
- "✍",
- "🧠",
- "👀",
- "👂",
- "👅",
- "🦷",
- "🐾",
- "🐶",
- "🦊",
- "🦝",
- "🐈",
- "🦁",
- "🐯",
- "🐴",
- "🦄",
- "🦓",
- "🐮",
- "🐷",
- "🐑",
- "🐪",
- "🐘",
- "🐭",
- "🐰",
- "🦔",
- "🦇",
- "🐻",
- "🐨",
- "🐼",
- "🐔",
- "🦨",
- "🦘",
- "🐦",
- "🐧",
- "🦩",
- "🦉",
- "🦜",
- "🪶",
- "🐸",
- "🐊",
- "🐢",
- "🦎",
- "🐍",
- "🦖",
- "🦀",
- "🐬",
- "🐙",
- "🐌",
- "🐝",
- "🐞",
- "🌸",
- "🌲",
- "🌵",
- "🍀",
- "🍁",
- "🍇",
- "🍉",
- "🍊",
- "🍋",
- "🍌",
- "🍍",
- "🍎",
- "🥥",
- "🍐",
- "🍒",
- "🍓",
- "🫐",
- "🥝",
- "🥔",
- "🥕",
- "🧅",
- "🌰",
- "🍄",
- "🍞",
- "🥞",
- "🧀",
- "🍖",
- "🍔",
- "🍟",
- "🍕",
- "🥚",
- "🍿",
- "🧂",
- "🍙",
- "🍦",
- "🍩",
- "🍪",
- "🎂",
- "🍬",
- "🍭",
- "🥛",
- "☕",
- "🫖",
- "🍾",
- "🍷",
- "🍹",
- "🍺",
- "🍴",
- "🥄",
- "🫙",
- "🧭",
- "🌋",
- "🪵",
- "🏡",
- "🏢",
- "🏰",
- "⛲",
- "⛺",
- "🎡",
- "🚂",
- "🚘",
- "🚜",
- "🚲",
- "🚔",
- "🚨",
- "⛽",
- "🚥",
- "🚧",
- "⚓",
- "⛵",
- "🛟",
- "🪂",
- "🚀",
- "⌛",
- "⏰",
- "🌂",
- "🌞",
- "🌙",
- "🌟",
- "⛅",
- "⚡",
- "🔥",
- "🌊",
- "🎃",
- "🎈",
- "🎉",
- "✨",
- "🎀",
- "🎁",
- "🏆",
- "🏅",
- "🔮",
- "🪄",
- "🎾",
- "🎳",
- "🎲",
- "🎭",
- "🎨",
- "🧵",
- "🎩",
- "📢",
- "🔔",
- "🎵",
- "🎤",
- "🎧",
- "🎷",
- "🎸",
- "🥁",
- "🔋",
- "🔌",
- "💻",
- "💾",
- "💿",
- "🎬",
- "📺",
- "📷",
- "🎮",
- "🧩",
- "🔍",
- "💡",
- "📖",
- "💰",
- "💼",
- "📈",
- "📌",
- "📎",
- "🔒",
- "🔑",
- "🔧",
- "🪛",
- "🔩",
- "🧲",
- "🔬",
- "🔭",
- "📡",
- "🚪",
- "🪑",
- "⛔",
- "🚩",
+const emojis = [
+ "👽","🤖","🧚","🧜","🏄","🐵","🦍","🐶","🐺","🦊","🐈","🦁","🐯","🐴","🦄","🦓",
+ "🦌","🐮","🐷","🐗","🐑","🦙","🦒","🐘","🐭","🐹","🐇","🐿","🦔","🐨","🐼","🦥",
+ "🦨","🦘","🐓","🐥","🐦","🐧","🕊","🦆","🦢","🦉","🦤","🦩","🦚","🦜","🐊","🐢",
+ "🦎","🐍","🐉","🦕","🦖","🐋","🐬","🐟","🐠","🐡","🦈","🐙","🐚","🐌","🦋","🐛",
+ "🐝","🐞","💐","🌸","🌹","🌺","🌻","🌼","🌷","🌱","🌲","🌳","🌴","🌵","🌿","🍁",
+ "🍇","🍉","🍊","🍋","🍌","🍍","🥭","🍏","🍐","🍑","🍒","🍓","🥝","🍅","🥥","🥑",
+ "🍆","🥕","🌽","🌶","🥬","🥦","🧅","🍄","🥜","🥐","🥖","🥨","🥞","🧇","🍔","🍕",
+ "🌭","🌮","🌯","🥚","🍿","🍙","🥟","🦀","🦞","🦑","🍦","🍩","🧁","🍬","🍭","🧃",
+ "🧉","🧭","⛰","🌋","🏝","🏡","⛲","⛺","🎠","🎡","💈","🚂","🚃","🚌","🚗","🚚",
+ "🚜","🛵","🛺","🚲","🛴","🛹","⚓️","⛵","🛶","🚤","🚢","✈️","🪂","🚁","🚠","🛰",
+ "🚀","🛸","⏳","🌙","🌡","☀️","🪐","⭐","☁️","🌧","🌩","🌀","🌈","☂️","❄️","☄️",
+ "🔥","💧","🌊","🎃","✨","🎈","🎉","🎊","🎏","🎟","🏆","⚽","🏀","🏈","🎾","🥏",
+ "🏓","⛸","🪀","🪁","🎱","🔮","🪄","🕹","🎲","🧩","🧸","🎨","🧵","🧶","🕶","🧦",
+ "🎒","👟","👠","👑","🎓","🧢","💍","💎","📢","🎵","🎙","🎤","🎧","📻","🎷","🪗",
+ "🎸","🎺","🎻","🪕","🥁","☎️","💿","🎥","🎬","📺","📷","🔍","💡","🔦","📖","📚",
+ "🏷","✏️","🖌","🖍","📎","📌","🔑","🪃","🏹","⚙️","🧲","🧪","🧬","🔭","📡","🗿",
];
+
// FNV-1a implementation that is compatible with other languages
const prime = 0x01000193;
const offset = 0x811c9dc5;
let hash = offset;
const encoder = new TextEncoder();
- for (const charCode of encoder.encode(bridgeString)) {
- hash = Math.imul(hash ^ charCode, prime);
+ for (const byte of encoder.encode(bridgeString)) {
+ hash = Math.imul(hash ^ byte, prime);
}
const hashBytes = [
diff --git a/browser/components/torpreferences/content/connectionPane.xhtml b/browser/components/torpreferences/content/connectionPane.xhtml
index 67f98685d8038..39a9c184502f0 100644
--- a/browser/components/torpreferences/content/connectionPane.xhtml
+++ b/browser/components/torpreferences/content/connectionPane.xhtml
@@ -93,6 +93,9 @@
<html:span id="torPreferences-currentBridges-headerText"/>
<html:input type="checkbox" id="torPreferences-currentBridges-switch" class="toggle-button"/>
</html:h2>
+ <description flex="1">
+ <html:span id="torPreferences-currentBridges-description"/>
+ </description>
<menupopup id="torPreferences-bridgeCard-menu"/>
<vbox id="torPreferences-bridgeCard-template" class="torPreferences-bridgeCard">
<hbox class="torPreferences-bridgeCard-heading">
@@ -174,4 +177,18 @@
</hbox>
</box>
</groupbox>
+
+<html:div id="bridge-remove-overlay" class="hidden">
+ <html:div id="bridge-remove-modal">
+ <html:img id="bridge-remove-dismiss" src="chrome://global/skin/icons/close.svg"/>
+ <html:div id="bridge-remove-icon"/>
+ <html:p id="bridge-remove-question"/>
+ <html:p id="bridge-remove-warning"/>
+ <html:div id="bridge-remove-buttonbar">
+ <button id="bridge-remove-cancel"/>
+ <button id="bridge-remove-confirm"/>
+ </html:div>
+ </html:div>
+</html:div>
+
</html:template>
diff --git a/browser/components/torpreferences/content/torPreferences.css b/browser/components/torpreferences/content/torPreferences.css
index 31b6e29d679f3..2ab29bcd60faa 100644
--- a/browser/components/torpreferences/content/torPreferences.css
+++ b/browser/components/torpreferences/content/torPreferences.css
@@ -176,6 +176,11 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
width: 280px;
}
+#torPreferences-bridges-location menuitem[disabled="true"] {
+ color: var(--in-content-button-text-color, inherit);
+ font-weight: 700;
+}
+
/* Bridge cards */
:root {
--bridgeCard-animation-time: 0.25s;
@@ -188,7 +193,7 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
}
#torPreferences-currentBridges-cards.list-collapsed {
- mask-image: linear-gradient(rgb(0, 0, 0), rgba(0, 0, 0, 0.1));
+ mask-image: linear-gradient(rgb(0, 0, 0) 0% 75%, rgba(0, 0, 0, 0.1));
}
#torPreferences-currentBridges-cards.disabled {
@@ -200,9 +205,12 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
/* define border-radius here because of the transition */
border-radius: 4px;
transition: margin var(--bridgeCard-animation-time), box-shadow 150ms;
+ cursor: pointer;
}
-.torPreferences-bridgeCard.expanded {
+.torPreferences-bridgeCard.expanded,
+.torPreferences-bridgeCard.currently-connected,
+.single-card .torPreferences-bridgeCard {
margin: 12px 0;
background: var(--in-content-box-background);
box-shadow: var(--card-shadow);
@@ -211,7 +219,11 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
.torPreferences-bridgeCard:hover {
background: var(--in-content-box-background);
box-shadow: var(--card-shadow-hover);
- cursor: pointer;
+}
+
+.single-card .torPreferences-bridgeCard,
+.torPreferences-bridgeCard.currently-connected {
+ cursor: default;
}
.torPreferences-bridgeCard-heading {
@@ -228,7 +240,7 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
padding: 4px;
font-size: 20px;
border-radius: 4px;
- background: var(--in-content-button-background-hover);
+ background: var(--in-content-box-background-odd);
}
.torPreferences-bridgeCard-headingAddr {
@@ -237,12 +249,15 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
width: 20px;
flex: 1;
margin: 0 8px;
- white-space: nowrap;
overflow: hidden;
+ color: var(--in-content-deemphasized-text);
+ white-space: nowrap;
text-overflow: ellipsis;
}
-.expanded .torPreferences-bridgeCard-headingAddr {
+.expanded .torPreferences-bridgeCard-headingAddr,
+.currently-connected .torPreferences-bridgeCard-headingAddr,
+.single-card .torPreferences-bridgeCard-headingAddr {
display: none;
}
@@ -354,10 +369,17 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
visibility: hidden;
}
-.expanded .torPreferences-bridgeCard-grid {
+.expanded .torPreferences-bridgeCard-grid,
+.currently-connected .torPreferences-bridgeCard-grid,
+.single-card .torPreferences-bridgeCard-grid {
visibility: visible;
}
+.currently-connected .torPreferences-bridgeCard-grid,
+.single-card .torPreferences-bridgeCard-grid {
+ height: auto;
+}
+
.torPreferences-bridgeCard-grid.to-animate {
transition: height var(--bridgeCard-animation-time) ease-out, visibility var(--bridgeCard-animation-time);
overflow: hidden;
@@ -375,8 +397,9 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before {
margin: 8px 0;
}
-.torPreferences-bridgeCard-addr {
+input.torPreferences-bridgeCard-addr {
width: 100%;
+ color: var(--in-content-deemphasized-text);
}
.torPreferences-bridgeCard-leranMoreBox {
@@ -539,3 +562,94 @@ textarea#torPreferences-torDialog-textarea {
/* 10 lines */
min-height: 20em;
}
+
+/* Bridge remove overlay */
+#bridge-remove-overlay {
+ position: fixed;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ top: 0;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ background-color: rgba(0, 0, 0, 0.5);
+}
+
+#bridge-remove-overlay.hidden {
+ display: none;
+}
+
+#bridge-remove-modal {
+ position: relative;
+ min-width: 250px;
+ max-width: 500px;
+ min-height: 200px;
+ z-index: 2;
+ text-align: center;
+ background: var(--in-content-page-background);
+ box-shadow: var(--shadow-30);
+}
+
+#bridge-remove-dismiss {
+ position: absolute;
+ top: 16px;
+ inset-inline-end: 16px;
+ width: 16px;
+ height: 16px;
+ fill: currentColor;
+ -moz-context-properties: fill;
+}
+
+#bridge-remove-dismiss:hover {
+ background-color: var(--in-content-button-background-hover);
+ color: var(--in-content-button-text-color-hover);
+ border: 1px solid var(--in-content-button-border-color-hover);
+ border-radius: 4px;
+}
+
+#bridge-remove-dismiss:hover:active {
+ background-color: var(--in-content-button-background-active);
+}
+
+#bridge-remove-icon {
+ width: 40px;
+ height: 40px;
+ background-image: url("chrome://global/skin/icons/warning.svg");
+ background-size: 40px;
+ margin: 16px auto;
+ fill: currentColor;
+ -moz-context-properties: fill;
+}
+
+#bridge-remove-question {
+ font-size: 150%;
+}
+
+#bridge-remove-warning {
+ color: var(--in-content-deemphasized-text);
+}
+
+#bridge-remove-buttonbar {
+ padding: 16px 32px;
+}
+
+#bridge-remove-buttonbar button {
+ min-width: 140px;
+}
+
+#bridge-remove-confirm {
+ background: var(--in-content-danger-button-background);
+ color: var(--in-content-primary-button-text-color);
+}
+
+#bridge-remove-confirm:hover {
+ background: var(--in-content-danger-button-background-hover);
+ color: var(--in-content-primary-button-text-color-hover);
+ border-color: var(--in-content-primary-button-border-hover);
+}
+
+#bridge-remove-confirm:hover:active {
+ background: var(--in-content-danger-button-background-active);
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits