[tbb-commits] [tor-browser] 21/33: fixup! squash! Bug 27476: Implement about:torconnect captive portal within Tor Browser
gitolite role
git at cupani.torproject.org
Tue May 3 22:40:29 UTC 2022
This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-91.9.0esr-11.5-1
in repository tor-browser.
commit 6a61028e16fc04260551649360ae878549f0b733
Author: Pier Angelo Vendrame <pierov at torproject.org>
AuthorDate: Thu Apr 14 11:55:13 2022 +0200
fixup! squash! Bug 27476: Implement about:torconnect captive portal within Tor Browser
Reworked how the UI states are managed and shown.
Also, the UI state is saved to TorConnect, so that reloading the page or
opening it in another tab/window shows the same state.
Syncrhonize the breadcrumb and location changes.
---
browser/components/torconnect/TorConnectParent.jsm | 25 ++
.../torconnect/content/aboutTorConnect.css | 2 +-
.../torconnect/content/aboutTorConnect.js | 309 +++++++++++++--------
toolkit/modules/RemotePageAccessManager.jsm | 7 +-
4 files changed, 229 insertions(+), 114 deletions(-)
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
index 528f48392880b..95eb9c975c7d5 100644
--- a/browser/components/torconnect/TorConnectParent.jsm
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -14,6 +14,8 @@ const { TorSettings, TorSettingsTopics, TorSettingsData } = ChromeUtils.import(
"resource:///modules/TorSettings.jsm"
);
+const BroadcastTopic = "about-torconnect:broadcast";
+
/*
This object is basically a marshalling interface between the TorConnect module
and a particular about:torconnect page
@@ -28,6 +30,7 @@ class TorConnectParent extends JSWindowActorParent {
this.state = {
State: TorConnect.state,
StateChanged: false,
+ PreviousState: TorConnectState.Initial,
ErrorMessage: TorConnect.errorMessage,
ErrorDetails: TorConnect.errorDetails,
BootstrapProgress: TorConnect.bootstrapProgress,
@@ -35,6 +38,7 @@ class TorConnectParent extends JSWindowActorParent {
InternetStatus: TorConnect.internetStatus,
ShowViewLog: TorConnect.logHasWarningOrError,
QuickStartEnabled: TorSettings.quickstart.enabled,
+ UIState: TorConnect.uiState,
};
// JSWindowActiveParent derived objects cannot observe directly, so create a member
@@ -52,6 +56,7 @@ class TorConnectParent extends JSWindowActorParent {
self.state.StateChanged = false;
switch (aTopic) {
case TorConnectTopics.StateChange: {
+ self.state.PreviousState = self.state.State;
self.state.State = obj.state;
self.state.StateChanged = true;
@@ -110,6 +115,17 @@ class TorConnectParent extends JSWindowActorParent {
this.torConnectObserver,
TorSettingsTopics.SettingChanged
);
+
+ this.userActionObserver = {
+ observe(aSubject, aTopic, aData) {
+ let obj = aSubject?.wrappedJSObject;
+ if (obj) {
+ obj.connState = self.state;
+ self.sendAsyncMessage("torconnect:user-action", obj);
+ }
+ },
+ };
+ Services.obs.addObserver(this.userActionObserver, BroadcastTopic);
}
willDestroy() {
@@ -122,6 +138,7 @@ class TorConnectParent extends JSWindowActorParent {
this.torConnectObserver,
TorSettingsTopics.SettingChanged
);
+ Services.obs.addObserver(this.userActionObserver, BroadcastTopic);
}
async receiveMessage(message) {
@@ -150,12 +167,20 @@ class TorConnectParent extends JSWindowActorParent {
Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
);
break;
+ case "torconnect:set-ui-state":
+ TorConnect.uiState = message.data;
+ this.state.UIState = TorConnect.uiState;
+ break;
+ case "torconnect:broadcast-user-action":
+ Services.obs.notifyObservers(message.data, BroadcastTopic);
+ break;
case "torconnect:get-init-args":
// called on AboutTorConnect.init(), pass down all state data it needs to init
// pretend this is a state transition on init
// so we always get fresh UI
this.state.StateChanged = true;
+ this.state.UIState = TorConnect.uiState;
return {
TorStrings,
TorConnectState,
diff --git a/browser/components/torconnect/content/aboutTorConnect.css b/browser/components/torconnect/content/aboutTorConnect.css
index 7f7d75010e82e..41cfe2bb80c70 100644
--- a/browser/components/torconnect/content/aboutTorConnect.css
+++ b/browser/components/torconnect/content/aboutTorConnect.css
@@ -274,7 +274,7 @@ body {
fill: var(--onion-color);
}
-.title.assit {
+.title.offline, .title.assit, .title.final {
background-image: url("chrome://browser/content/torconnect/connection-failure.svg");
}
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js
index e525949c40d88..506b125e8b98f 100644
--- a/browser/components/torconnect/content/aboutTorConnect.js
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -7,6 +7,15 @@ let TorStrings = {};
let TorConnectState = {};
let InternetStatus = {};
+const UIStates = Object.freeze({
+ ConnectToTor: "ConnectToTor",
+ Offline: "Offline",
+ ConnectionAssist: "ConnectionAssist",
+ CouldNotLocate: "CouldNotLocate",
+ LocationConfirm: "LocationConfirm",
+ FinalError: "FinalError",
+});
+
const BreadcrumbStatus = Object.freeze({
Hidden: "hidden",
Disabled: "disabled",
@@ -130,28 +139,34 @@ class AboutTorConnect {
// forward us to once bootstrap completes (otherwise the window will just close)
redirect = null;
- showNext = state => {};
-
- allowAutomaticLocation = true;
-
- bootstrappingTitle = "";
- bootstrappingDescription = "";
- bootstrappingBreadcrumb = -1;
+ uiState = {
+ currentState: UIStates.ConnectToTor,
+ connectIsTryAgain: false,
+ allowAutomaticLocation: true,
+ selectedLocation: "automatic",
+ bootstrapCause: UIStates.ConnectToTor,
+ };
locations = {};
+ constructor() {
+ this.uiStates = Object.freeze(
+ Object.fromEntries([
+ [UIStates.ConnectToTor, this.showConnectToTor.bind(this)],
+ [UIStates.Offline, this.showOffline.bind(this)],
+ [UIStates.ConnectionAssist, this.showConnectionAssistant.bind(this)],
+ [UIStates.CouldNotLocate, this.showCouldNotLocate.bind(this)],
+ [UIStates.LocationConfirm, this.showLocationConfirmation.bind(this)],
+ [UIStates.FinalError, this.showFinalError.bind(this)],
+ ])
+ );
+ }
+
beginBootstrap() {
- this.hide(this.elements.connectButton);
- this.hide(this.elements.quickstartContainer);
- this.show(this.elements.cancelButton);
- this.elements.cancelButton.focus();
RPMSendAsyncMessage("torconnect:begin-bootstrap");
}
beginAutoBootstrap(countryCode) {
- this.hide(this.elements.tryBridgeButton);
- this.show(this.elements.cancelButton);
- this.elements.cancelButton.focus();
if (countryCode === "automatic") {
countryCode = "";
}
@@ -162,6 +177,18 @@ class AboutTorConnect {
RPMSendAsyncMessage("torconnect:cancel-bootstrap");
}
+ transitionUIState(nextState, connState) {
+ if (nextState !== this.uiState.currentState) {
+ this.uiState.currentState = nextState;
+ this.saveUIState();
+ }
+ this.uiStates[nextState](connState);
+ }
+
+ saveUIState() {
+ RPMSendAsyncMessage("torconnect:set-ui-state", this.uiState);
+ }
+
/*
Element helper methods
*/
@@ -176,6 +203,7 @@ class AboutTorConnect {
}
hideButtons() {
+ this.hide(this.elements.quickstartContainer);
this.hide(this.elements.restartButton);
this.hide(this.elements.configureButton);
this.hide(this.elements.cancelButton);
@@ -337,20 +365,17 @@ class AboutTorConnect {
/* Per-state updates */
update_Initial(state) {
- this.showConnectToTor(state, false);
+ this.showConnectToTor(state);
}
update_Configuring(state) {
- this.hide(this.elements.quickstartContainer);
- this.hide(this.elements.viewLogContainer);
- this.hideButtons();
-
- if (state.ErrorMessage === null) {
- this.showConnectToTor(state, false);
- } else if (state.InternetStatus === InternetStatus.Offline) {
- this.showOffline(state.ErrorMessage);
- } else {
- this.showNext(state);
+ if (
+ state.StateChanged &&
+ (state.PreviousState === TorConnectState.Bootstrapping ||
+ state.PreviousState === TorConnectState.AutoBootstrapping)
+ ) {
+ // The bootstrap has been cancelled
+ this.transitionUIState(this.uiState.bootstrapCause, state);
}
}
@@ -363,13 +388,36 @@ class AboutTorConnect {
}
update_Error(state) {
- const showProgressbar = false;
-
- this.setTitle(state.ErrorMessage, "error");
- this.setLongText("");
- this.setProgress(state.ErrorDetails, showProgressbar);
- this.hideButtons();
- this.show(this.elements.viewLogContainer);
+ if (!this.uiState.connectIsTryAgain) {
+ // TorConnect.hasBootstrapEverFailed remains false in case of Internet
+ // offline
+ this.uiState.connectIsTryAgain = true;
+ this.saveUIState();
+ }
+ if (!state.StateChanged) {
+ return;
+ }
+ if (state.InternetStatus === InternetStatus.Offline) {
+ this.transitionUIState(UIStates.Offline, state);
+ } else if (state.PreviousState === TorConnectState.Bootstrapping) {
+ this.transitionUIState(UIStates.ConnectionAssist, state);
+ } else if (state.PreviousState === TorConnectState.AutoBootstrapping) {
+ if (this.uiState.bootstrapCause === UIStates.ConnectionAssist) {
+ this.transitionUIState(
+ this.getLocation() === "automatic"
+ ? UIStates.CouldNotLocate
+ : UIStates.LocationConfirm,
+ state
+ );
+ } else {
+ this.transitionUIState(UIStates.FinalError, state);
+ }
+ } else {
+ console.error(
+ "We received an error starting from an unexpected state",
+ state
+ );
+ }
}
update_Bootstrapped(state) {
@@ -391,10 +439,11 @@ class AboutTorConnect {
// it isn't in use (eg using tor-launcher or system tor)
}
- showConnectToTor(state, tryAgain) {
+ showConnectToTor(state) {
this.setTitle(TorStrings.torConnect.torConnect, "");
this.setLongText(TorStrings.settings.torPreferencesDescription);
this.setProgress("", false);
+ this.hide(this.elements.viewLogContainer);
this.hideButtons();
this.show(this.elements.quickstartContainer);
this.show(this.elements.configureButton);
@@ -402,7 +451,7 @@ class AboutTorConnect {
if (state?.StateChanged) {
this.elements.connectButton.focus();
}
- if (tryAgain) {
+ if (this.uiState.connectIsTryAgain) {
this.setBreadcrumbsStatus(
BreadcrumbStatus.Active,
BreadcrumbStatus.Default,
@@ -410,38 +459,53 @@ class AboutTorConnect {
);
this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
}
- this.bootstrappingDescription =
- TorStrings.settings.torPreferencesDescription;
- this.showNext = fromState => {
- this.showConnectionAssistant(fromState.ErrorDetails);
- if (fromState.StateChanged) {
- this.elements.tryBridgeButton.focus();
- }
- };
+ this.uiState.bootstrapCause = UIStates.ConnectToTor;
+ this.saveUIState();
}
showBootstrapping(state) {
const showProgressbar = true;
- this.setTitle(this.bootstrappingTitle, "");
- this.showConfigureConnectionLink(this.bootstrappingDescription);
- this.setProgress("", showProgressbar, state.BootstrapProgress);
- if (this.bootstrappingBreadcrumb < 0) {
- this.hideBreadcrumbs();
- } else {
- const breadcrumbs = [
- BreadcrumbStatus.Disabled,
- BreadcrumbStatus.Disabled,
- BreadcrumbStatus.Disabled,
- ];
- breadcrumbs[this.bootstrappingBreadcrumb] = BreadcrumbStatus.Active;
- this.setBreadcrumbsStatus(...breadcrumbs);
+ let title = "";
+ let description = "";
+ const breadcrumbs = [
+ BreadcrumbStatus.Disabled,
+ BreadcrumbStatus.Disabled,
+ BreadcrumbStatus.Disabled,
+ ];
+ switch (this.uiState.bootstrapCause) {
+ case UIStates.ConnectToTor:
+ breadcrumbs[0] = BreadcrumbStatus.Active;
+ title = this.uiState.connectIsTryAgain
+ ? TorStrings.torConnect.tryAgain
+ : TorStrings.torConnect.torConnecting;
+ description = TorStrings.settings.torPreferencesDescription;
+ break;
+ case UIStates.ConnectionAssist:
+ breadcrumbs[2] = BreadcrumbStatus.Active;
+ title = TorStrings.torConnect.tryingBridge;
+ description = TorStrings.torConnect.assistDescription;
+ break;
+ case UIStates.CouldNotLocate:
+ breadcrumbs[2] = BreadcrumbStatus.Active;
+ title = TorStrings.torConnect.tryingBridgeAgain;
+ description = TorStrings.torConnect.errorLocationDescription;
+ break;
+ case UIStates.LocationConfirm:
+ breadcrumbs[2] = BreadcrumbStatus.Active;
+ title = TorStrings.torConnect.tryingBridgeAgain;
+ description = TorStrings.torConnect.isLocationCorrectDescription;
+ break;
}
+ this.setTitle(title, "");
+ this.showConfigureConnectionLink(description);
+ this.setProgress("", showProgressbar, state.BootstrapProgress);
+ this.setBreadcrumbsStatus(...breadcrumbs);
+ this.hideButtons();
if (state.ShowViewLog) {
this.show(this.elements.viewLogContainer);
} else {
this.hide(this.elements.viewLogContainer);
}
- this.hideButtons();
this.show(this.elements.cancelButton, true);
if (state.StateChanged) {
this.elements.cancelButton.focus();
@@ -449,7 +513,7 @@ class AboutTorConnect {
}
showOffline(error) {
- this.setTitle(TorStrings.torConnect.noInternet, "error");
+ this.setTitle(TorStrings.torConnect.noInternet, "offline");
this.setLongText("Some long text from 🍩️");
this.setProgress(error, false);
this.setBreadcrumbsStatus(
@@ -457,82 +521,73 @@ class AboutTorConnect {
BreadcrumbStatus.Active,
BreadcrumbStatus.Hidden
);
+ this.show(this.elements.viewLogContainer);
this.hideButtons();
this.show(this.elements.configureButton);
this.show(this.elements.connectButton, true);
this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
}
- showConnectionAssistant(errorMessage) {
+ showConnectionAssistant(state) {
this.setTitle(TorStrings.torConnect.couldNotConnect, "assit");
this.showConfigureConnectionLink(TorStrings.torConnect.assistDescription);
- this.setProgress(errorMessage, false);
+ this.setProgress(state?.ErrorDetails, false);
this.setBreadcrumbsStatus(
BreadcrumbStatus.Default,
BreadcrumbStatus.Active,
BreadcrumbStatus.Disabled
);
this.showLocationForm(false, TorStrings.torConnect.tryBridge);
- this.bootstrappingBreadcrumb = 2;
- this.bootstrappingTitle = TorStrings.torConnect.tryingBridge;
- this.bootstrappingDescription = TorStrings.torConnect.assistDescription;
- this.showNext = state => {
- if (this.getLocation() === "automatic") {
- this.showCannotLocate(state.ErrorMessage);
- } else {
- this.showLocationConfirmation(state.ErrorMessage);
- }
- if (state.StateChanged) {
- this.elements.tryBridgeButton.focus();
- }
- };
+ if (state?.StateChanged) {
+ this.elements.tryBridgeButton.focus();
+ }
+ this.uiState.bootstrapCause = UIStates.ConnectionAssist;
+ this.saveUIState();
}
- showCannotLocate(errorMessage) {
- this.allowAutomaticLocation = false;
+ showCouldNotLocate(state) {
+ this.uiState.allowAutomaticLocation = false;
this.setTitle(TorStrings.torConnect.errorLocation, "location");
this.showConfigureConnectionLink(
TorStrings.torConnect.errorLocationDescription
);
- this.setProgress(errorMessage, false);
+ this.setProgress(state.ErrorMessage, false);
this.setBreadcrumbsStatus(
BreadcrumbStatus.Default,
BreadcrumbStatus.Active,
BreadcrumbStatus.Disabled
);
+ this.show(this.elements.viewLogContainer);
this.showLocationForm(true, TorStrings.torConnect.tryBridge);
- this.bootstrappingBreadcrumb = 2;
- this.bootstrappingTitle = TorStrings.torConnect.tryingBridgeAgain;
- this.bootstrappingDescription =
- TorStrings.torConnect.errorLocationDescription;
- this.showNext = state => {
- this.showFinalError(state);
- };
+ if (state.StateChanged) {
+ this.elements.tryBridgeButton.focus();
+ }
+ this.uiState.bootstrapCause = UIStates.CouldNotLocate;
+ this.saveUIState();
}
- showLocationConfirmation(errorMessage) {
+ showLocationConfirmation(state) {
this.setTitle(TorStrings.torConnect.isLocationCorrect, "location");
this.showConfigureConnectionLink(
TorStrings.torConnect.isLocationCorrectDescription
);
- this.setProgress(errorMessage, false);
+ this.setProgress(state.ErrorMessage, false);
this.setBreadcrumbsStatus(
BreadcrumbStatus.Default,
BreadcrumbStatus.Default,
BreadcrumbStatus.Active
);
+ this.show(this.elements.viewLogContainer);
this.showLocationForm(true, TorStrings.torConnect.tryAgain);
- this.bootstrappingBreadcrumb = 2;
- this.bootstrappingTitle = TorStrings.torConnect.tryingBridgeAgain;
- this.bootstrappingDescription =
- TorStrings.torConnect.isLocationCorrectDescription;
- this.showNext = state => {
- this.showFinalError(state);
- };
+ if (state.StateChanged) {
+ this.elements.tryBridgeButton.focus();
+ }
+ this.uiState.bootstrapCause = UIStates.LocationConfirm;
+ this.saveUIState();
}
showFinalError(state) {
- this.setTitle(TorStrings.torConnect.finalError, "error");
+ this.setTitle(TorStrings.torConnect.finalError, "final");
this.setLongText(TorStrings.torConnect.finalErrorDescription);
this.setProgress(state ? state.ErrorDetails : "", false);
this.setBreadcrumbsStatus(
@@ -543,9 +598,6 @@ class AboutTorConnect {
this.hideButtons();
this.show(this.elements.restartButton);
this.show(this.elements.configureButton, true);
- this.showNext = fromState => {
- this.showFinalError(fromState);
- };
}
showConfigureConnectionLink(text) {
@@ -570,16 +622,18 @@ class AboutTorConnect {
RPMSendQuery("torconnect:get-country-codes").then(codes => {
if (codes && codes.length) {
this.populateFrequentLocations(codes);
+ this.setLocationFromState();
}
});
let firstOpt = this.elements.locationDropdownSelect.options[0];
- if (this.allowAutomaticLocation) {
+ if (this.uiState.allowAutomaticLocation) {
firstOpt.value = "automatic";
firstOpt.textContent = TorStrings.torConnect.automatic;
} else {
firstOpt.value = "";
firstOpt.textContent = TorStrings.torConnect.selectCountryRegion;
}
+ this.setLocationFromState();
this.validateLocation();
this.show(this.elements.locationDropdownLabel);
this.show(this.elements.locationDropdown);
@@ -596,20 +650,34 @@ class AboutTorConnect {
return this.elements.locationDropdownSelect.options[selectedIndex].value;
}
+ setLocationFromState() {
+ if (this.getLocation() === this.uiState.selectedLocation) {
+ return;
+ }
+ const options = this.elements.locationDropdownSelect.options;
+ // We need to do this way, because we have repeated values that break
+ // the .value way to select (which would however require the label,
+ // rather than the code)...
+ for (let i = 0; i < options.length; i++) {
+ if (options[i].value === this.uiState.selectedLocation) {
+ this.elements.locationDropdownSelect.selectedIndex = i;
+ break;
+ }
+ }
+ this.validateLocation();
+ }
+
initElements(direction) {
document.documentElement.setAttribute("dir", direction);
- this.bootstrappingTitle = TorStrings.torConnect.torConnecting;
-
this.elements.connectToTorLink.addEventListener("click", event => {
- if (
- this.elements.connectToTorLink.classList.contains(
- BreadcrumbStatus.Active
- )
- ) {
+ if (this.uiState.currentState === UIStates.ConnectToTor) {
return;
}
- this.showConnectToTor(null, true);
+ this.transitionUIState(UIStates.ConnectToTor, null);
+ RPMSendAsyncMessage("torconnect:broadcast-user-action", {
+ uiState: UIStates.ConnectToTor,
+ });
});
this.elements.connectToTorLabel.textContent =
TorStrings.torConnect.torConnect;
@@ -624,7 +692,10 @@ class AboutTorConnect {
) {
return;
}
- this.showConnectionAssistant();
+ this.transitionUIState(UIStates.ConnectionAssist, null);
+ RPMSendAsyncMessage("torconnect:broadcast-user-action", {
+ uiState: UIStates.ConnectionAssist,
+ });
});
this.elements.connectionAssistLabel.textContent =
TorStrings.torConnect.breadcrumbAssist;
@@ -664,19 +735,17 @@ class AboutTorConnect {
this.elements.connectButton.textContent =
TorStrings.torConnect.torConnectButton;
this.elements.connectButton.addEventListener("click", () => {
- if (
- this.elements.connectButton.textContent ===
- TorStrings.torConnect.tryAgain
- ) {
- this.bootstrappingBreadcrumb = 0;
- this.bootstrappingTitle = TorStrings.torConnect.tryingAgain;
- }
this.beginBootstrap();
});
this.populateLocations();
this.elements.locationDropdownSelect.addEventListener("change", () => {
+ this.uiState.selectedLocation = this.getLocation();
+ this.saveUIState();
this.validateLocation();
+ RPMSendAsyncMessage("torconnect:broadcast-user-action", {
+ location: this.uiState.selectedLocation,
+ });
});
this.elements.locationDropdownLabel.textContent =
@@ -698,6 +767,15 @@ class AboutTorConnect {
RPMAddMessageListener("torconnect:state-change", ({ data }) => {
this.updateUI(data);
});
+ RPMAddMessageListener("torconnect:user-action", ({ data }) => {
+ if (data.location) {
+ this.uiState.selectedLocation = data.location;
+ this.setLocationFromState();
+ }
+ if (data.uiState !== undefined) {
+ this.transitionUIState(data.uiState, data.connState);
+ }
+ });
}
initKeyboardShortcuts() {
@@ -735,6 +813,13 @@ class AboutTorConnect {
this.initObservers();
this.initKeyboardShortcuts();
+ if (Object.keys(args.State.UIState).length) {
+ this.uiState = args.State.UIState;
+ } else {
+ args.State.UIState = this.uiState;
+ this.saveUIState();
+ }
+ this.uiStates[this.uiState.currentState](args.State);
// populate UI based on current state
this.updateUI(args.State);
}
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 5ddf546ce12de..c195ab688b236 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -220,7 +220,10 @@ let RemotePageAccessManager = {
RPMSendQuery: ["FetchUpdateData"],
},
"about:torconnect": {
- RPMAddMessageListener: ["torconnect:state-change"],
+ RPMAddMessageListener: [
+ "torconnect:state-change",
+ "torconnect:user-action",
+ ],
RPMSendAsyncMessage: [
"torconnect:open-tor-preferences",
"torconnect:begin-bootstrap",
@@ -229,6 +232,8 @@ let RemotePageAccessManager = {
"torconnect:set-quickstart",
"torconnect:view-tor-logs",
"torconnect:restart",
+ "torconnect:set-ui-state",
+ "torconnect:broadcast-user-action",
],
RPMSendQuery: [
"torconnect:get-init-args",
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the tbb-commits
mailing list