[tbb-commits] [tor-browser/tor-browser-78.12.0esr-11.0-1] fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
sysrqb at torproject.org
sysrqb at torproject.org
Thu Jul 22 17:46:25 UTC 2021
commit 73d57c01c2bfe398c02586ae76dab677d843df22
Author: Richard Pospesel <richard at torproject.org>
Date: Fri Jul 16 17:32:01 2021 +0200
fixup! Bug 27476: Implement about:torconnect captive portal within Tor Browser
---
browser/actors/NetErrorParent.jsm | 6 +-
browser/base/content/browser.js | 5 +-
browser/components/BrowserGlue.jsm | 32 +-
browser/components/torconnect/TorConnectParent.jsm | 200 +++++----
.../torconnect/content/aboutTorConnect.js | 466 +++++++++-----------
.../torconnect/content/aboutTorConnect.xhtml | 9 -
.../torconnect/content/torBootstrapUrlbar.js | 195 ++++-----
.../components/torpreferences/content/torPane.js | 8 +-
browser/components/urlbar/UrlbarInput.jsm | 6 +-
browser/modules/TorConnect.jsm | 477 +++++++++++++++++++--
browser/modules/TorProtocolService.jsm | 101 +++--
.../processsingleton/MainProcessSingleton.jsm | 5 +
toolkit/modules/RemotePageAccessManager.jsm | 28 +-
13 files changed, 941 insertions(+), 597 deletions(-)
diff --git a/browser/actors/NetErrorParent.jsm b/browser/actors/NetErrorParent.jsm
index fa3cbf23fcb7..6dce9af5aad0 100644
--- a/browser/actors/NetErrorParent.jsm
+++ b/browser/actors/NetErrorParent.jsm
@@ -17,8 +17,8 @@ const { SessionStore } = ChromeUtils.import(
);
const { HomePage } = ChromeUtils.import("resource:///modules/HomePage.jsm");
-const { TorProtocolService } = ChromeUtils.import(
- "resource:///modules/TorProtocolService.jsm"
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
);
const PREF_SSL_IMPACT_ROOTS = [
@@ -324,7 +324,7 @@ class NetErrorParent extends JSWindowActorParent {
}
break;
case "ShouldShowTorConnect":
- return TorProtocolService.shouldShowTorConnect();
+ return TorConnect.shouldShowTorConnect;
}
return undefined;
}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 916cd69320cb..996ef6dcdd7f 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -77,7 +77,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabModalPrompt: "chrome://global/content/tabprompts.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
- TorProtocolService: "resource:///modules/TorProtocolService.jsm",
+ TorConnect: "resource:///modules/TorConnect.jsm",
Translation: "resource:///modules/translation/TranslationParent.jsm",
OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UITour: "resource:///modules/UITour.jsm",
@@ -2494,7 +2494,8 @@ var gBrowserInit = {
let uri = window.arguments[0];
let defaultArgs = BrowserHandler.defaultArgs;
- if (TorProtocolService.shouldShowTorConnect()) {
+ if (TorConnect.shouldShowTorConnect) {
+ TorConnect.setURIsToLoad(uri);
return "about:torconnect";
}
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 8735783cee2b..cb77f4d82a3e 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -17,31 +17,6 @@ const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
-// TorProtocolService and TorConnect modules need to be lazily-loaded
-// here because they will trigger generation of the random password used
-// to talk to the tor daemon in tor-launcher. Generating the random
-// password will initialize the cryptographic service ( nsNSSComponent )
-//
-// If this service is init'd before the profile has been setup, it will
-// use the fallback init path which behaves as if security.nocertdb=true
-//
-// We make these module getters so init happens when they are needed
-// (when init'ing the OnionAliasStore). With theze getters, the password
-// generation is triggered in torbutton after the 'profile-after-change'
-// topic (so after the profile is initialized)
-
-ChromeUtils.defineModuleGetter(
- this,
- "TorProtocolService",
- "resource:///modules/TorProtocolService.jsm"
-);
-
-ChromeUtils.defineModuleGetter(
- this,
- "TorConnect",
- "resource:///modules/TorConnect.jsm"
-);
-
ChromeUtils.defineModuleGetter(
this,
"ActorManagerParent",
@@ -2531,14 +2506,17 @@ BrowserGlue.prototype = {
{
task: () => {
- if (TorProtocolService.isBootstrapDone() || !TorProtocolService.ownsTorDaemon) {
+ const { TorConnect, TorConnectTopics } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+ );
+ if (!TorConnect.shouldShowTorConnect) {
// we will take this path when the user is using the legacy tor launcher or
// when Tor Browser didn't launch its own tor.
OnionAliasStore.init();
} else {
// this path is taken when using about:torconnect, we wait to init
// after we are bootstrapped and connected to tor
- const topic = "torconnect:bootstrap-complete";
+ const topic = TorConnectTopics.BootstrapComplete;
let bootstrapObserver = {
observe(aSubject, aTopic, aData) {
if (aTopic === topic) {
diff --git a/browser/components/torconnect/TorConnectParent.jsm b/browser/components/torconnect/TorConnectParent.jsm
index c34fab76ddbb..3937bf3ebcf8 100644
--- a/browser/components/torconnect/TorConnectParent.jsm
+++ b/browser/components/torconnect/TorConnectParent.jsm
@@ -3,123 +3,139 @@
var EXPORTED_SYMBOLS = ["TorConnectParent"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { TorProtocolService } = ChromeUtils.import(
- "resource:///modules/TorProtocolService.jsm"
-);
const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
-const { TorLauncherUtil } = ChromeUtils.import(
- "resource://torlauncher/modules/tl-util.jsm"
-);
-
-const { TorConnect } = ChromeUtils.import(
+const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
"resource:///modules/TorConnect.jsm"
);
-const kTorProcessReadyTopic = "TorProcessIsReady";
-const kTorProcessExitedTopic = "TorProcessExited";
-const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
-const kTorShowProgressPanelTopic = "TorShowProgressPanel";
-const kTorBootstrapStatusTopic = "TorBootstrapStatus";
-const kTorBootstrapErrorTopic = "TorBootstrapError";
-const kTorLogHasWarnOrErrTopic = "TorLogHasWarnOrErr";
-
-const gActiveTopics = [
- kTorProcessReadyTopic,
- kTorProcessExitedTopic,
- kTorProcessDidNotStartTopic,
- kTorShowProgressPanelTopic,
- kTorBootstrapStatusTopic,
- kTorBootstrapErrorTopic,
- kTorLogHasWarnOrErrTopic,
- "torconnect:bootstrap-complete",
-];
-
-const gTorLauncherPrefs = {
+const TorLauncherPrefs = Object.freeze({
quickstart: "extensions.torlauncher.quickstart",
-}
+});
+
+/*
+This object is basically a marshalling interface between the TorConnect module
+and a particular about:torconnect page
+*/
class TorConnectParent extends JSWindowActorParent {
constructor(...args) {
super(...args);
const self = this;
- this.gObserver = {
- observe(aSubject, aTopic, aData) {
- const obj = aSubject?.wrappedJSObject;
- if (obj) {
- obj.handled = true;
- }
- self.sendAsyncMessage(aTopic, obj);
- },
- };
- for (const topic of gActiveTopics) {
- Services.obs.addObserver(this.gObserver, topic);
- }
+ this.state = {
+ State: TorConnect.state,
+ ErrorMessage: TorConnect.errorMessage,
+ ErrorDetails: TorConnect.errorDetails,
+ BootstrapProgress: TorConnect.bootstrapProgress,
+ BootstrapStatus: TorConnect.bootstrapStatus,
+ ShowCopyLog: TorConnect.logHasWarningOrError,
+ QuickStartEnabled: Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false),
+ };
- this.quickstartObserver = {
+ // JSWindowActiveParent derived objects cannot observe directly, so create a member
+ // object to do our observing for us
+ //
+ // This object converts the various lifecycle events from the TorConnect module, and
+ // maintains a state object which we pass down to our about:torconnect page, which uses
+ // the state object to update its UI
+ this.torConnectObserver = {
observe(aSubject, aTopic, aData) {
- if (aTopic === "nsPref:changed" &&
- aData == gTorLauncherPrefs.quickstart) {
- self.sendAsyncMessage("TorQuickstartPrefChanged", Services.prefs.getBoolPref(gTorLauncherPrefs.quickstart));
+ let obj = aSubject?.wrappedJSObject;
+
+ // update our state struct based on received torconnect topics and forward on
+ // to aboutTorConnect.js
+ switch(aTopic) {
+ case TorConnectTopics.StateChange: {
+ self.state.State = obj.state;
+ // clear any previous error information if we are bootstrapping
+ if (self.state.State === TorConnectState.Bootstrapping) {
+ self.state.ErrorMessage = null;
+ self.state.ErrorDetails = null;
+ }
+ break;
+ }
+ case TorConnectTopics.BootstrapProgress: {
+ self.state.BootstrapProgress = obj.progress;
+ self.state.BootstrapStatus = obj.status;
+ self.state.ShowCopyLog = obj.hasWarnings;
+ break;
+ }
+ case TorConnectTopics.BootstrapComplete: {
+ // tells about:torconnect pages to close themselves
+ // this flag will only be set if an about:torconnect page
+ // reaches the Bootstrapped state, so if a user
+ // navigates to about:torconnect manually after bootstrap, the page
+ // will not auto-close on them
+ self.state.Close = true;
+ break;
+ }
+ case TorConnectTopics.BootstrapError: {
+ self.state.ErrorMessage = obj.message;
+ self.state.ErrorDetails = obj.details;
+ self.state.ShowCopyLog = true;
+ break;
+ }
+ case TorConnectTopics.FatalError: {
+ // TODO: handle
+ break;
+ }
+ case "nsPref:changed": {
+ if (aData === TorLauncherPrefs.quickstart) {
+ self.state.QuickStartEnabled = Services.prefs.getBoolPref(TorLauncherPrefs.quickstart);
+ }
+ break;
+ }
+ default: {
+ console.log(`TorConnect: unhandled observe topic '${aTopic}'`);
+ }
}
+
+ self.sendAsyncMessage("torconnect:state-change", self.state);
},
+ };
+
+ // observe all of the torconnect:.* topics
+ for (const key in TorConnectTopics) {
+ const topic = TorConnectTopics[key];
+ Services.obs.addObserver(this.torConnectObserver, topic);
}
- Services.prefs.addObserver(gTorLauncherPrefs.quickstart, this.quickstartObserver);
+ Services.prefs.addObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
}
willDestroy() {
- for (const topic of gActiveTopics) {
- Services.obs.removeObserver(this.gObserver, topic);
+ // stop observing all of our torconnect:.* topics
+ for (const key in TorConnectTopics) {
+ const topic = TorConnectTopics[key];
+ Services.obs.removeObserver(this.torConnectObserver, topic);
}
- }
-
-
- _OpenTorAdvancedPreferences() {
- const win = this.browsingContext.top.embedderElement.ownerGlobal;
- win.openTrustedLinkIn("about:preferences#tor", "tab");
- }
-
- _TorCopyLog() {
- // Copy tor log messages to the system clipboard.
- const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
- Ci.nsIClipboardHelper
- );
- const countObj = { value: 0 };
- chSvc.copyString(TorProtocolService.getLog(countObj));
- const count = countObj.value;
- return TorLauncherUtil.getFormattedLocalizedString(
- "copiedNLogMessagesShort",
- [count],
- 1
- );
+ Services.prefs.removeObserver(TorLauncherPrefs.quickstart, this.torConnectObserver);
}
receiveMessage(message) {
switch (message.name) {
- case "TorBootstrapErrorOccurred":
- return TorProtocolService.torBootstrapErrorOccurred();
- case "TorRetrieveBootstrapStatus":
- return TorProtocolService.retrieveBootstrapStatus();
- case "OpenTorAdvancedPreferences":
- return this._OpenTorAdvancedPreferences();
- case "GetLocalizedBootstrapStatus":
- const { status, keyword } = message.data;
- return TorLauncherUtil.getLocalizedBootstrapStatus(status, keyword);
- case "TorCopyLog":
- return this._TorCopyLog();
- case "TorIsNetworkDisabled":
- return TorProtocolService.isNetworkDisabled();
- case "TorStopBootstrap":
- return TorProtocolService.torStopBootstrap();
- case "TorConnect":
- return TorProtocolService.connect();
- case "GetDirection":
- return Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
- case "GetTorStrings":
- return TorStrings;
- case "TorLogHasWarnOrErr":
- return TorProtocolService.torLogHasWarnOrErr();
+ case "torconnect:set-quickstart":
+ Services.prefs.setBoolPref(TorLauncherPrefs.quickstart, message.data);
+ break;
+ case "torconnect:open-tor-preferences":
+ TorConnect.openTorPreferences();
+ break;
+ case "torconnect:copy-tor-logs":
+ return TorConnect.copyTorLogs();
+ case "torconnect:cancel-bootstrap":
+ TorConnect.cancelBootstrap();
+ break;
+ case "torconnect:begin-bootstrap":
+ TorConnect.beginBootstrap();
+ break;
+ case "torconnect:get-init-args":
+ // called on AboutTorConnect.init(), pass down all state data it needs to init
+ return {
+ TorStrings: TorStrings,
+ TorConnectState: TorConnectState,
+ Direction: Services.locale.isAppLocaleRTL ? "rtl" : "ltr",
+ State: this.state,
+ };
}
return undefined;
}
diff --git a/browser/components/torconnect/content/aboutTorConnect.js b/browser/components/torconnect/content/aboutTorConnect.js
index 19fd335ccd13..8b269d2fc82b 100644
--- a/browser/components/torconnect/content/aboutTorConnect.js
+++ b/browser/components/torconnect/content/aboutTorConnect.js
@@ -2,299 +2,258 @@
/* eslint-env mozilla/frame-script */
-const kTorProcessReadyTopic = "TorProcessIsReady";
-const kTorProcessExitedTopic = "TorProcessExited";
-const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
-const kTorBootstrapStatusTopic = "TorBootstrapStatus";
-const kTorBootstrapErrorTopic = "TorBootstrapError";
-const kTorLogHasWarnOrErrTopic = "TorLogHasWarnOrErr";
-const kTorQuickstartPrefChanged = "TorQuickstartPrefChanged";
-
-const TorLauncherPrefs = {
- quickstart: "extensions.torlauncher.quickstart",
- prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
-}
+// populated in AboutTorConnect.init()
+let TorStrings = {};
+let TorConnectState = {};
class AboutTorConnect {
- log(...args) {
- console.log(...args);
- }
-
- logError(...args) {
- console.error(...args);
- }
+ selectors = Object.freeze({
+ textContainer: {
+ title: "div.title",
+ titleText: "h1.title-text",
+ },
+ progress: {
+ description: "p#connectShortDescText",
+ meter: "div#progressBackground",
+ },
+ copyLog: {
+ link: "span#copyLogLink",
+ tooltip: "div#copyLogTooltip",
+ tooltipText: "span#copyLogTooltipText",
+ },
+ quickstart: {
+ checkbox: "input#quickstartCheckbox",
+ label: "label#quickstartCheckboxLabel",
+ },
+ buttons: {
+ connect: "button#connectButton",
+ cancel: "button#cancelButton",
+ advanced: "button#advancedButton",
+ },
+ })
+
+ elements = Object.freeze({
+ title: document.querySelector(this.selectors.textContainer.title),
+ titleText: document.querySelector(this.selectors.textContainer.titleText),
+ progressDescription: document.querySelector(this.selectors.progress.description),
+ progressMeter: document.querySelector(this.selectors.progress.meter),
+ copyLogLink: document.querySelector(this.selectors.copyLog.link),
+ copyLogTooltip: document.querySelector(this.selectors.copyLog.tooltip),
+ copyLogTooltipText: document.querySelector(this.selectors.copyLog.tooltipText),
+ quickstartCheckbox: document.querySelector(this.selectors.quickstart.checkbox),
+ quickstartLabel: document.querySelector(this.selectors.quickstart.label),
+ connectButton: document.querySelector(this.selectors.buttons.connect),
+ cancelButton: document.querySelector(this.selectors.buttons.cancel),
+ advancedButton: document.querySelector(this.selectors.buttons.advanced),
+ })
+
+ beginBootstrap() {
+ this.hide(this.elements.connectButton);
+ this.show(this.elements.cancelButton);
+ this.elements.cancelButton.focus();
+ RPMSendAsyncMessage("torconnect:begin-bootstrap");
+ }
+
+ cancelBootstrap() {
+ RPMSendAsyncMessage("torconnect:cancel-bootstrap");
+ }
+
+ /*
+ Element helper methods
+ */
+
+ show(element) {
+ element.removeAttribute("hidden");
+ }
+
+ hide(element) {
+ element.setAttribute("hidden", "true");
+ }
+
+ setTitle(title, error) {
+ this.elements.titleText.textContent = title;
+ document.title = title;
- logDebug(...args) {
- console.debug(...args);
+ if (error) {
+ this.elements.title.classList.add("error");
+ } else {
+ this.elements.title.classList.remove("error");
+ }
}
- getElem(id) {
- return document.getElementById(id);
- }
- get elemProgressContent() {
- return this.getElem("progressContent");
- }
- get elemProgressDesc() {
- return this.getElem("connectShortDescText");
- }
- get elemProgressMeter() {
- return this.getElem("progressBackground");
- }
- get elemCopyLogLink() {
- return this.getElem("copyLogLink");
- }
- get elemCopyLogTooltip() {
- return this.getElem("copyLogTooltip");
- }
- get elemCopyLogTooltipText() {
- return this.getElem("copyLogTooltipText");
- }
- get elemQuickstartCheckbox() {
- return this.getElem("quickstartCheckbox");
- }
- get elemQuickstartLabel() {
- return this.getElem("quickstartCheckboxLabel");
- }
- get elemConnectButton() {
- return this.getElem("connectButton");
- }
- get elemAdvancedButton() {
- return this.getElem("advancedButton");
- }
- get elemCancelButton() {
- return this.getElem("cancelButton");
- }
- get elemTextContainer() {
- return this.getElem("text-container");
- }
- get elemTitle() {
- return this.elemTextContainer.getElementsByClassName("title")[0];
+ setProgress(description, visible, percent) {
+ this.elements.progressDescription.textContent = description;
+ if (visible) {
+ this.show(this.elements.progressMeter);
+ this.elements.progressMeter.style.width = `${percent}%`;
+ } else {
+ this.hide(this.elements.progressMeter);
+ }
}
- static get STATE_INITIAL() {
- return "STATE_INITIAL";
- }
+ /*
+ These methods update the UI based on the current TorConnect state
+ */
- static get STATE_BOOTSTRAPPING() {
- return "STATE_BOOTSTRAPPING";
- }
+ updateUI(state) {
+ console.log(state);
- static get STATE_BOOTSTRAPPED() {
- return "STATE_BOOTSTRAPPED";
- }
+ // calls update_$state()
+ this[`update_${state.State}`](state);
- static get STATE_BOOTSTRAP_ERROR() {
- return "STATE_BOOTSTRAP_ERROR";
+ if (state.ShowCopyLog) {
+ this.showCopyLog();
+ }
+ this.elements.quickstartCheckbox.checked = state.QuickStartEnabled;
}
- get state() {
- return this._state;
- }
+ /* Per-state updates */
- setInitialUI() {
- this.setTitle(this.torStrings.torConnect.torConnect);
- this.elemProgressDesc.textContent =
- this.torStrings.settings.torPreferencesDescription;
- this.showElem(this.elemConnectButton);
- this.elemConnectButton.focus();
- this.showElem(this.elemAdvancedButton);
- this.hideElem(this.elemCopyLogLink);
- this.hideElem(this.elemCancelButton);
- this.hideElem(this.elemProgressContent);
- this.hideElem(this.elemProgressMeter);
- this.elemTitle.classList.remove("error");
- }
+ update_Initial(state) {
+ const hasError = false;
+ const showProgressbar = false;
- setBootstrappingUI() {
- this.setTitle(this.torStrings.torConnect.torConnecting);
- this.hideElem(this.elemConnectButton);
- this.hideElem(this.elemAdvancedButton);
- this.hideElem(this.elemCopyLogLink);
- this.showElem(this.elemCancelButton);
- this.elemCancelButton.focus();
- this.showElem(this.elemProgressContent);
- this.showElem(this.elemProgressMeter);
- this.elemTitle.classList.remove("error");
+ this.setTitle(TorStrings.torConnect.torConnect, hasError);
+ this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar);
+ this.hide(this.elements.copyLogLink);
+ this.hide(this.elements.connectButton);
+ this.hide(this.elements.advancedButton);
+ this.hide(this.elements.cancelButton);
}
- setBootstrapErrorUI() {
- this.setTitle(this.torStrings.torConnect.torBootstrapFailed);
- this.elemConnectButton.textContent = this.torStrings.torConnect.tryAgain;
- this.showElem(this.elemConnectButton);
- this.hideElem(this.elemCancelButton);
- this.showElem(this.elemAdvancedButton);
- this.elemAdvancedButton.focus();
- this.showElem(this.elemProgressContent);
- this.hideElem(this.elemProgressMeter);
- this.elemTitle.classList.add("error");
- }
+ update_Configuring(state) {
+ const hasError = state.ErrorMessage != null;
+ const showProgressbar = false;
- set state(state) {
- const oldState = this.state;
- if (oldState === state) {
- return;
- }
- this._state = state;
- switch (this.state) {
- case AboutTorConnect.STATE_INITIAL:
- this.setInitialUI();
- break;
- case AboutTorConnect.STATE_BOOTSTRAPPING:
- this.setBootstrappingUI();
- break;
- case AboutTorConnect.STATE_BOOTSTRAP_ERROR:
- this.setBootstrapErrorUI();
- break;
- case AboutTorConnect.STATE_BOOTSTRAPPED:
- window.close();
- break;
+ if (hasError) {
+ this.setTitle(state.ErrorMessage, hasError);
+ this.setProgress(state.ErrorDetails, showProgressbar);
+ this.show(this.elements.copyLogLink);
+ this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+ } else {
+ this.setTitle(TorStrings.torConnect.torConnect, hasError);
+ this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar);
+ this.hide(this.elements.copyLogLink);
+ this.elements.connectButton.textContent = TorStrings.torConnect.torConnectButton;
}
+ this.show(this.elements.connectButton);
+ this.elements.connectButton.focus();
+ this.show(this.elements.advancedButton);
+ this.hide(this.elements.cancelButton);
}
- async showErrorMessage(aErrorObj) {
- if (aErrorObj && aErrorObj.message) {
- this.setTitle(aErrorObj.message);
- if (aErrorObj.details) {
- this.elemProgressDesc.textContent = aErrorObj.details;
- }
- }
-
- this.showCopyLog();
- this.showElem(this.elemConnectButton);
+ update_AutoConfiguring(state) {
+ // TODO: noop until this state is used
}
- showElem(elem) {
- elem.removeAttribute("hidden");
- }
+ update_Bootstrapping(state) {
+ const hasError = false;
+ const showProgressbar = true;
- hideElem(elem) {
- elem.setAttribute("hidden", "true");
+ this.setTitle(state.BootstrapStatus ? state.BootstrapStatus : TorStrings.torConnect.torConnecting, hasError);
+ this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar, state.BootstrapProgress);
+ if (state.ShowCopyLog) {
+ this.show(this.elements.copyLogLink);
+ } else {
+ this.hide(this.elements.copyLogLink);
+ }
+ this.hide(this.elements.connectButton);
+ this.hide(this.elements.advancedButton);
+ this.show(this.elements.cancelButton);
+ this.elements.cancelButton.focus();
}
- async connect() {
- // reset the text to original description
- // in case we are trying again after an error (clears out error text)
- this.elemProgressDesc.textContent =
- this.torStrings.settings.torPreferencesDescription;
+ update_Error(state) {
+ const hasError = true;
+ const showProgressbar = false;
- this.state = AboutTorConnect.STATE_BOOTSTRAPPING;
- const error = await RPMSendQuery("TorConnect");
- if (error) {
- if (error.details) {
- this.showErrorMessage({ message: error.details }, true);
- this.showSaveSettingsError(error.details);
- }
- }
+ this.setTitle(state.ErrorMessage, hasError);
+ this.setProgress(state.ErrorDetails, showProgressbar);
+ this.show(this.elements.copyLogLink);
+ this.elements.connectButton.textContent = TorStrings.torConnect.tryAgain;
+ this.show(this.elements.connectButton);
+ this.show(this.elements.advancedButton);
+ this.hide(this.elements.cancelButton);
}
- showCopyLog() {
- this.elemCopyLogLink.removeAttribute("hidden");
+ update_FatalError(state) {
+ // TODO: noop until this state is used
}
- async updateBootstrapProgress(status) {
- let labelText = await RPMSendQuery("GetLocalizedBootstrapStatus", {
- status,
- keyword: "TAG",
- });
- let percentComplete = status.PROGRESS ? status.PROGRESS : 0;
- this.elemProgressMeter.style.width = `${percentComplete}%`;
-
- if (await RPMSendQuery("TorBootstrapErrorOccurred")) {
- this.state = AboutTorConnect.STATE_BOOTSTRAP_ERROR;
- return;
- } else if (await RPMSendQuery("TorIsNetworkDisabled")) {
- // If tor network is not connected, let's go to the initial state, even
- // if bootstrap state is greater than 0.
- this.state = AboutTorConnect.STATE_INITIAL;
- return;
- } else if (percentComplete > 0) {
- this.state = AboutTorConnect.STATE_BOOTSTRAPPING;
- }
+ update_Bootstrapped(state) {
+ const hasError = false;
+ const showProgressbar = true;
- // Due to async, status might have changed. Do not override desc if so.
- if (this.state === AboutTorConnect.STATE_BOOTSTRAPPING) {
- this.hideElem(this.elemConnectButton);
+ this.setTitle(TorStrings.torConnect.torConnected, hasError);
+ this.setProgress(TorStrings.settings.torPreferencesDescription, showProgressbar, 100);
+ this.hide(this.elements.connectButton);
+ this.hide(this.elements.advancedButton);
+ this.hide(this.elements.cancelButton);
+
+ // only close the window if directed
+ if (state.Close) {
+ window.close();
}
}
- stopTorBootstrap() {
- RPMSendAsyncMessage("TorStopBootstrap");
+ update_Disabled(state) {
+ // TODO: we should probably have some UX here if a user goes to about:torconnect when
+ // it isn't in use (eg using tor-launcher or system tor)
}
- setTitle(title) {
- const titleElement = document.querySelector(".title-text");
- titleElement.textContent = title;
- document.title = title;
- }
+ async initElements(direction, quickstart) {
- async initElements() {
- this.elemAdvancedButton.textContent = this.torStrings.torConnect.torConfigure;
- this.elemAdvancedButton.addEventListener("click", () => {
- RPMSendAsyncMessage("OpenTorAdvancedPreferences");
- });
+ document.documentElement.setAttribute("dir", direction);
- // sets the text content while keping the child elements intact
- this.elemCopyLogLink.childNodes[0].nodeValue =
- this.torStrings.torConnect.copyLog;
- this.elemCopyLogLink.addEventListener("click", async (event) => {
- const copiedMessage = await RPMSendQuery("TorCopyLog");
- aboutTorConnect.elemCopyLogTooltipText.textContent = copiedMessage;
- aboutTorConnect.elemCopyLogTooltip.style.visibility = "visible";
+ // sets the text content while keeping the child elements intact
+ this.elements.copyLogLink.childNodes[0].nodeValue =
+ TorStrings.torConnect.copyLog;
+ this.elements.copyLogLink.addEventListener("click", async (event) => {
+ const copiedMessage = await RPMSendQuery("torconnect:copy-tor-logs");
+ this.elements.copyLogTooltipText.textContent = copiedMessage;
+ this.elements.copyLogTooltipText.style.visibility = "visible";
// clear previous timeout if one already exists
- if (aboutTorConnect.copyLogTimeoutId) {
- clearTimeout(aboutTorConnect.copyLogTimeoutId);
+ if (this.copyLogTimeoutId) {
+ clearTimeout(this.copyLogTimeoutId);
}
// hide tooltip after X ms
const TOOLTIP_TIMEOUT = 2000;
- aboutTorConnect.copyLogTimeoutId = setTimeout(function() {
- aboutTorConnect.elemCopyLogTooltip.style.visibility = "hidden";
- aboutTorConnect.copyLogTimeoutId = 0;
+ this.copyLogTimeoutId = setTimeout(function() {
+ this.elements.copyLogTooltipText.style.visibility = "hidden";
+ this.copyLogTimeoutId = 0;
}, TOOLTIP_TIMEOUT);
});
+ this.elements.quickstartCheckbox.checked = quickstart
+ this.elements.quickstartCheckbox.addEventListener("change", () => {
+ const quickstart = this.elements.quickstartCheckbox.checked;
+ RPMSendAsyncMessage("torconnect:set-quickstart", quickstart);
+ });
+ this.elements.quickstartLabel.textContent = TorStrings.settings.quickstartCheckbox;
- this.elemQuickstartLabel.textContent = this.torStrings.settings.quickstartCheckbox;
- this.elemQuickstartCheckbox.addEventListener("change", () => {
- const quickstart = this.elemQuickstartCheckbox.checked;
- RPMSetBoolPref(TorLauncherPrefs.quickstart, quickstart);
+ this.elements.connectButton.textContent =
+ TorStrings.torConnect.torConnectButton;
+ this.elements.connectButton.addEventListener("click", () => {
+ this.beginBootstrap();
});
- this.elemQuickstartCheckbox.checked = await RPMGetBoolPref(TorLauncherPrefs.quickstart);
- this.elemConnectButton.textContent =
- this.torStrings.torConnect.torConnectButton;
- this.elemConnectButton.addEventListener("click", () => {
- this.connect();
+ this.elements.advancedButton.textContent = TorStrings.torConnect.torConfigure;
+ this.elements.advancedButton.addEventListener("click", () => {
+ RPMSendAsyncMessage("torconnect:open-tor-preferences");
});
- this.elemCancelButton.textContent = this.torStrings.torConnect.cancel;
- this.elemCancelButton.addEventListener("click", () => {
- this.stopTorBootstrap();
+ this.elements.cancelButton.textContent = TorStrings.torConnect.cancel;
+ this.elements.cancelButton.addEventListener("click", () => {
+ this.cancelBootstrap();
});
}
initObservers() {
- RPMAddMessageListener(kTorBootstrapErrorTopic, ({ data }) => {
- this.showCopyLog();
- this.stopTorBootstrap();
- this.showErrorMessage(data);
- });
- RPMAddMessageListener(kTorLogHasWarnOrErrTopic, () => {
- this.showCopyLog();
- });
- RPMAddMessageListener(kTorProcessDidNotStartTopic, ({ data }) => {
- this.showErrorMessage(data);
- });
- RPMAddMessageListener(kTorBootstrapStatusTopic, ({ data }) => {
- this.updateBootstrapProgress(data);
- });
- RPMAddMessageListener(kTorQuickstartPrefChanged, ({ data }) => {
- // update checkbox with latest quickstart pref value
- this.elemQuickstartCheckbox.checked = data;
- });
- RPMAddMessageListener("torconnect:bootstrap-complete", () => {
- this.state = AboutTorConnect.STATE_BOOTSTRAPPED;
+ // TorConnectParent feeds us state blobs to we use to update our UI
+ RPMAddMessageListener("torconnect:state-change", ({ data }) => {
+ this.updateUI(data);
});
}
@@ -304,34 +263,25 @@ class AboutTorConnect {
// integers, so we must resort to a string compare here :(
// see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code for relevant documentation
if (evt.code === "Escape") {
- this.stopTorBootstrap();
+ this.cancelBootstrap();
}
};
}
async init() {
- this.torStrings = await RPMSendQuery("GetTorStrings");
- document.documentElement.setAttribute(
- "dir",
- await RPMSendQuery("GetDirection")
- );
- this.initElements();
+
+ let args = await RPMSendQuery("torconnect:get-init-args");
+
+ // various constants
+ TorStrings = Object.freeze(args.TorStrings);
+ TorConnectState = Object.freeze(args.TorConnectState);
+
+ this.initElements(args.Direction);
this.initObservers();
this.initKeyboardShortcuts();
- this.state = AboutTorConnect.STATE_INITIAL;
-
- // Request the most recent bootstrap status info so that a
- // TorBootstrapStatus notification is generated as soon as possible.
- RPMSendAsyncMessage("TorRetrieveBootstrapStatus");
-
- // quickstart is the user set pref for starting tor automatically
- // prompt_at_startup will be set to false after successful bootstrap, and true on error
- // by tor-launcher, so we want to keep the connect screen up when prompt_at_startup is true
- /// even if quickstart is enabled so user can potentially resolve errors on next launch
- if (await RPMGetBoolPref(TorLauncherPrefs.quickstart) &&
- !await RPMGetBoolPref(TorLauncherPrefs.prompt_at_startup)) {
- this.connect();
- }
+
+ // populate UI based on current state
+ this.updateUI(args.State);
}
}
diff --git a/browser/components/torconnect/content/aboutTorConnect.xhtml b/browser/components/torconnect/content/aboutTorConnect.xhtml
index 0a0721afb7db..595bbdf9a70a 100644
--- a/browser/components/torconnect/content/aboutTorConnect.xhtml
+++ b/browser/components/torconnect/content/aboutTorConnect.xhtml
@@ -19,15 +19,6 @@
</div>
</div>
- <div id="progressContent" hidden="true">
- <div class="tbb-header" pack="center">
- <image class="tbb-logo"/>
- </div>
- <div flex="1">
- <div id="progressDesc"/>
- </div>
- </div>
-
<div id="copyLogContainer">
<span id="copyLogLink" hidden="true">
<div id="copyLogTooltip">
diff --git a/browser/components/torconnect/content/torBootstrapUrlbar.js b/browser/components/torconnect/content/torBootstrapUrlbar.js
index 55a595b2dbab..e4fd6f5ab910 100644
--- a/browser/components/torconnect/content/torBootstrapUrlbar.js
+++ b/browser/components/torconnect/content/torBootstrapUrlbar.js
@@ -2,135 +2,88 @@
"use strict";
- const TorConnectionStatus = {
- invalid: -1,
- offline: 0,
- connecting: 1,
- connected: 2,
- failure: 3,
- };
-var TorBootstrapUrlbar;
+const { TorConnect, TorConnectTopics, TorConnectState } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+const { TorStrings } = ChromeUtils.import(
+ "resource:///modules/TorStrings.jsm"
+);
-{
- const { TorProtocolService } = ChromeUtils.import(
- "resource:///modules/TorProtocolService.jsm"
- );
- const { TorLauncherUtil } = ChromeUtils.import(
- "resource://torlauncher/modules/tl-util.jsm"
- );
- const { TorStrings } = ChromeUtils.import(
- "resource:///modules/TorStrings.jsm"
- );
-
- const kTorProcessReadyTopic = "TorProcessIsReady";
- const kTorProcessExitedTopic = "TorProcessExited";
- const kTorProcessDidNotStartTopic = "TorProcessDidNotStart";
- const kTorBootstrapStatusTopic = "TorBootstrapStatus";
- const kTorBootstrapErrorTopic = "TorBootstrapError";
-
- const gActiveTopics = [
- kTorProcessReadyTopic,
- kTorProcessExitedTopic,
- kTorProcessDidNotStartTopic,
- kTorBootstrapStatusTopic,
- kTorBootstrapErrorTopic,
- ];
-
- TorBootstrapUrlbar = {
- _connectionStatus: TorConnectionStatus.invalid,
- get ConnectionStatus() {
- return this._connectionStatus;
+var TorBootstrapUrlbar = {
+ selectors: Object.freeze({
+ torConnect: {
+ box: "hbox#torconnect-box",
+ label: "label#torconnect-label",
},
+ }),
- _torConnectBox : null,
- get TorConnectBox() {
- if (!this._torConnectBox) {
- this._torConnectBox =
- browser.ownerGlobal.document.getElementById("torconnect-box");
- }
- return this._torConnectBox;
- },
+ elements: null,
- _torConnectLabel : null,
- get TorConnectLabel() {
- if (!this._torConnectLabel) {
- this._torConnectLabel =
- browser.ownerGlobal.document.getElementById("torconnect-label");
+ updateTorConnectBox: function(state) {
+ switch(state)
+ {
+ case TorConnectState.Initial:
+ case TorConnectState.Configuring:
+ case TorConnectState.AutoConfiguring:
+ case TorConnectState.Error:
+ case TorConnectState.FatalError: {
+ this.elements.torConnectBox.removeAttribute("hidden");
+ this.elements.torConnectLabel.textContent = TorStrings.torConnect.offline;
+ this.elements.inputContainer.setAttribute("torconnect", "offline");
+ break;
}
- return this._torConnectLabel;
- },
-
- _updateConnectionStatus(percentComplete = 0) {
- if (TorProtocolService.ownsTorDaemon &&
- !TorLauncherUtil.useLegacyLauncher) {
- if (TorProtocolService.isNetworkDisabled()) {
- if (TorProtocolService.torBootstrapErrorOccurred()) {
- this._connectionStatus = TorConnectionStatus.failure;
- } else {
- this._connectionStatus = TorConnectionStatus.offline;
- }
- } else if (percentComplete < 100) {
- this._connectionStatus = TorConnectionStatus.connecting;
- } else if (percentComplete === 100) {
- this._connectionStatus = TorConnectionStatus.connected;
- }
+ case TorConnectState.Bootstrapping: {
+ this.elements.torConnectBox.removeAttribute("hidden");
+ this.elements.torConnectLabel.textContent =
+ TorStrings.torConnect.torConnectingConcise;
+ this.elements.inputContainer.setAttribute("torconnect", "connecting");
+ break;
}
- else
- {
- this._connectionStatus = TorConnectionStatus.invalid;
+ case TorConnectState.Bootstrapped: {
+ this.elements.torConnectBox.removeAttribute("hidden");
+ this.elements.torConnectLabel.textContent =
+ TorStrings.torConnect.torConnectedConcise;
+ this.elements.inputContainer.setAttribute("torconnect", "connected");
+ // hide torconnect box after 5 seconds
+ setTimeout(() => {
+ this.elements.torConnectBox.setAttribute("hidden", "true");
+ }, 5000);
+ break;
}
-
- switch(this._connectionStatus)
- {
- case TorConnectionStatus.failure:
- case TorConnectionStatus.offline:
- this.TorConnectBox.removeAttribute("hidden");
- this.TorConnectLabel.textContent = TorStrings.torConnect.offline;
- gURLBar._inputContainer.setAttribute("torconnect", "offline");
- break;
- case TorConnectionStatus.connecting:
- this.TorConnectLabel.textContent =
- TorStrings.torConnect.torConnectingConcise;
- gURLBar._inputContainer.setAttribute("torconnect", "connecting");
- break;
- case TorConnectionStatus.connected:
- this.TorConnectLabel.textContent =
- TorStrings.torConnect.torConnectedConcise;
- gURLBar._inputContainer.setAttribute("torconnect", "connected");
- // hide torconnect box after 5 seconds
- let self = this;
- setTimeout(function() {
- self.TorConnectBox.setAttribute("hidden", "true");
- }, 5000);
- break;
+ case TorConnectState.Disabled: {
+ this.elements.torConnectBox.setAttribute("hidden", "true");
+ break;
}
- },
+ default:
+ break;
+ }
+ },
- observe(aSubject, aTopic, aData) {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic === TorConnectTopics.StateChange) {
const obj = aSubject?.wrappedJSObject;
+ this.updateTorConnectBox(obj?.state);
+ }
+ },
+
+ init: function() {
+ if (TorConnect.shouldShowTorConnect) {
+ // browser isn't populated until init
+ this.elements = Object.freeze({
+ torConnectBox: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.box),
+ torConnectLabel: browser.ownerGlobal.document.querySelector(this.selectors.torConnect.label),
+ inputContainer: gURLBar._inputContainer,
+ })
+ Services.obs.addObserver(this, TorConnectTopics.StateChange);
+ this.observing = true;
+ this.updateTorConnectBox(TorConnect.state);
+ }
+ },
+
+ uninit: function() {
+ if (this.observing) {
+ Services.obs.removeObserver(this, TorConnectTopics.StateChange);
+ }
+ },
+};
- switch (aTopic) {
- case kTorProcessReadyTopic:
- case kTorProcessExitedTopic:
- case kTorProcessDidNotStartTopic:
- case kTorBootstrapErrorTopic:
- this._updateConnectionStatus();
- break;
- case kTorBootstrapStatusTopic:
- let percentComplete = obj.PROGRESS ? obj.PROGRESS : 0;
- this._updateConnectionStatus(percentComplete);
- break;
- }
- },
- init() {
- for (const topic of gActiveTopics) {
- Services.obs.addObserver(this, topic);
- }
- },
- uninit() {
- for (const topic of gActiveTopics) {
- Services.obs.removeObserver(this, topic);
- }
- },
- };
-}
diff --git a/browser/components/torpreferences/content/torPane.js b/browser/components/torpreferences/content/torPane.js
index 01609ddda090..59ecdec6d1d9 100644
--- a/browser/components/torpreferences/content/torPane.js
+++ b/browser/components/torpreferences/content/torPane.js
@@ -6,6 +6,10 @@ const { TorProtocolService } = ChromeUtils.import(
"resource:///modules/TorProtocolService.jsm"
);
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+);
+
const {
TorBridgeSource,
TorBridgeSettings,
@@ -188,14 +192,14 @@ const gTorPane = (function() {
this._messageBoxButton = prefpane.querySelector(selectors.messageBox.button);
// wire up connect button
this._messageBoxButton.addEventListener("click", () => {
- TorProtocolService.connect();
+ TorConnect.beginBootstrap();
let win = Services.wm.getMostRecentWindow("navigator:browser");
// switch to existing about:torconnect tab or create a new one
win.switchToTabHavingURI("about:torconnect", true);
});
let populateMessagebox = () => {
- if (TorProtocolService.shouldShowTorConnect()) {
+ if (TorConnect.shouldShowTorConnect) {
// set messagebox style and text
if (TorProtocolService.torBootstrapErrorOccurred()) {
this._messageBox.className = "error";
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index f727c386701c..60b5b9163d67 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -10,8 +10,8 @@ const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
-const { TorProtocolService } = ChromeUtils.import(
- "resource:///modules/TorProtocolService.jsm"
+const { TorConnect } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
);
// in certain scenarios we want user input uris to open in a new tab if they do so from the
@@ -24,7 +24,7 @@ function maybeUpdateOpenLocationForTorConnect(openUILinkWhere, currentURI, desti
// we are trying to open in same tab
openUILinkWhere === "current" &&
// only if user still has not bootstrapped
- TorProtocolService.shouldShowTorConnect() &&
+ TorConnect.shouldShowTorConnect &&
// and user is not just navigating to about:torconnect
destinationURI !== "about:torconnect") {
return "tab";
diff --git a/browser/modules/TorConnect.jsm b/browser/modules/TorConnect.jsm
index 3125c84558db..5d2b826cfa10 100644
--- a/browser/modules/TorConnect.jsm
+++ b/browser/modules/TorConnect.jsm
@@ -1,6 +1,6 @@
"use strict";
-var EXPORTED_SYMBOLS = ["TorConnect"];
+var EXPORTED_SYMBOLS = ["TorConnect", "TorConnectTopics", "TorConnectState"];
const { Services } = ChromeUtils.import(
"resource://gre/modules/Services.jsm"
@@ -10,53 +10,476 @@ const { BrowserWindowTracker } = ChromeUtils.import(
"resource:///modules/BrowserWindowTracker.jsm"
);
-const { TorProtocolService } = ChromeUtils.import(
+const { TorProtocolService, TorProcessStatus } = ChromeUtils.import(
"resource:///modules/TorProtocolService.jsm"
);
-// TODO: move the bootstrap state management out of each of the individual
-// about:torconnect pages and stick it here
-var TorConnect = (() => {
+const { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+);
+
+/* Browser observer topis */
+const BrowserTopics = Object.freeze({
+ ProfileAfterChange: "profile-after-change",
+});
+
+/* tor-launcher observer topics */
+const TorTopics = Object.freeze({
+ ProcessIsReady: "TorProcessIsReady",
+ BootstrapStatus: "TorBootstrapStatus",
+ BootstrapError: "TorBootstrapError",
+ ProcessExited: "TorProcessExited",
+ LogHasWarnOrErr: "TorLogHasWarnOrErr",
+});
+
+/* Relevant prefs used by tor-launcher */
+const TorLauncherPrefs = Object.freeze({
+ quickstart: "extensions.torlauncher.quickstart",
+ prompt_at_startup: "extensions.torlauncher.prompt_at_startup",
+});
+
+const TorConnectState = Object.freeze({
+ /* Our initial state */
+ Initial: "Initial",
+ /* In-between initial boot and bootstrapping, users can change tor network settings during this state */
+ Configuring: "Configuring",
+ /* Geo-location and setting bridges/etc */
+ AutoConfiguring: "AutoConfiguring",
+ /* Tor is bootstrapping */
+ Bootstrapping: "Bootstrapping",
+ /* Passthrough state back to Configuring or Fatal */
+ Error: "Error",
+ /* An unrecoverable error */
+ FatalError: "FatalError",
+ /* Final state, after successful bootstrap */
+ Bootstrapped: "Bootstrapped",
+ /* If we are using System tor or the legacy Tor-Launcher */
+ Disabled: "Disabled",
+});
+
+/*
+
+ TorConnect State Transitions
+
+ ââââââââââââââââââââââââ
+ â Disabled â
+ ââââââââââââââââââââââââ
+ â²
+ â legacyOrSystemTor()
+ â
+ ââââââââââââââââââââââââ
+ âââââââââââââââââââââââ â Initial â ââââââââââââââââââââââââââââ
+ â ââââââââââââââââââââââââ â
+ â â â
+ â â beginBootstrap() â
+ â â¼ â
+ââââââââââââââââââ â bootstrapComplete() ââââââââââââââââââââââââââââââââââââââââââââââââââ â beginBootstrap()
+â Bootstrapped â ââââ¼ââââââââââââââââââââââ â Bootstrapping â ââ¼ââââââââââââââââââ
+ââââââââââââââââââ â ââââââââââââââââââââââââââââââââââââââââââââââââââ â â
+ â â â² â â â
+ â â cancelBootstrap() â beginBootstrap() ââââââ¼ââââââââââââââ â
+ â â¼ â â â â
+ â beginConfigure() ââââââââââââââââââââââââââââââââââââââââââââââââââ â â â
+ ââââââââââââââââââââââⶠâ â â â â
+ â â â â â
+ beginConfigure() â â â â â
+ âââââââââââââââââââââââââââⶠâ Configuring â â â â
+ â â â â â â
+ â â â â â â
+ â ââââââââââââââââââââââⶠâ â â â â
+ â â ââââââââââââââââââââââââââââââââââââââââââââââââââ â â â
+ â â â â â â â
+ â â cancelAutoconfigure() â autoConfigure() â ââââââ¼ââââââââââââââ¼ââââ
+ â â â¼ â â â â
+ â â ââââââââââââââââââââââââ â â â â
+ â âââââââââââââââââââââââ â AutoConfiguring â ââ¼âââââââââââââââââââââ â â
+ â ââââââââââââââââââââââââ â â â
+ â â â â onError() â
+ â â onError() â onError() â â
+ â â¼ â¼ â â
+ â ââââââââââââââââââââââââââââââââââââââââââââââââââ â â
+ ââââââââââââââââââââââââââââ â Error â ââ â
+ ââââââââââââââââââââââââââââââââââââââââââââââââââ â
+ â â² onError() â
+ â onFatalError() ââââââââââââââââââââ
+ â¼
+ ââââââââââââââââââââââââ
+ â FatalError â
+ ââââââââââââââââââââââââ
+
+*/
+
+
+/* Maps allowed state transitions
+ TorConnectStateTransitions[state] maps to an array of allowed states to transition to
+*/
+const TorConnectStateTransitions =
+ Object.freeze(new Map([
+ [TorConnectState.Initial,
+ [TorConnectState.Disabled,
+ TorConnectState.Bootstrapping,
+ TorConnectState.Configuring,
+ TorConnectState.Error]],
+ [TorConnectState.Configuring,
+ [TorConnectState.AutoConfiguring,
+ TorConnectState.Bootstrapping,
+ TorConnectState.Error]],
+ [TorConnectState.AutoConfiguring,
+ [TorConnectState.Configuring,
+ TorConnectState.Bootstrapping,
+ TorConnectState.Error]],
+ [TorConnectState.Bootstrapping,
+ [TorConnectState.Configuring,
+ TorConnectState.Bootstrapped,
+ TorConnectState.Error]],
+ [TorConnectState.Error,
+ [TorConnectState.Configuring,
+ TorConnectState.FatalError]],
+ // terminal states
+ [TorConnectState.FatalError, []],
+ [TorConnectState.Bootstrapped, []],
+ [TorConnectState.Disabled, []],
+ ]));
+
+/* Topics Notified by the TorConnect module */
+const TorConnectTopics = Object.freeze({
+ StateChange: "torconnect:state-change",
+ BootstrapProgress: "torconnect:bootstrap-progress",
+ BootstrapComplete: "torconnect:bootstrap-complete",
+ BootstrapError: "torconnect:bootstrap-error",
+ FatalError: "torconnect:fatal-error",
+});
+
+const TorConnect = (() => {
let retval = {
- init : function() {
- let topics = [
- "TorBootstrapStatus",
- ];
- for(const topic of topics) {
- Services.obs.addObserver(this, topic);
+ _state: TorConnectState.Initial,
+ _bootstrapProgress: 0,
+ _bootstrapStatus: null,
+ _errorMessage: null,
+ _errorDetails: null,
+ _logHasWarningOrError: false,
+ // init to about:tor as fallback in case setURIsToLoad is somehow never called
+ _urisToLoad: ["about:tor"],
+
+ /* These functions are called after transitioning to a new state */
+ _transitionCallbacks: Object.freeze(new Map([
+ /* Initial is never transitioned to */
+ [TorConnectState.Initial, null],
+ /* Configuring */
+ [TorConnectState.Configuring, (self) => {
+ // TODO move this to the transition function
+ if (this._state === TorConnectState.Bootstrapping) {
+ TorProtocolService.torStopBootstrap();
+ }
+ }],
+ /* AutoConfiguring */
+ [TorConnectState.AutoConfiguring, (self) => {
+
+ }],
+ /* Bootstrapping */
+ [TorConnectState.Bootstrapping, (self) => {
+ let error = TorProtocolService.connect();
+ if (error) {
+ self.onError(error.message, error.details);
+ } else {
+ self._errorMessage = self._errorDetails = null;
+ }
+ }],
+ /* Bootstrapped */
+ [TorConnectState.Bootstrapped, (self) => {
+ // open home page(s) in new tabs
+ const win = BrowserWindowTracker.getTopWindow()
+
+ let location="tab";
+ for (const uri of self._urisToLoad) {
+ win.openTrustedLinkIn(uri, location);
+ // open subsequent tabs behind first tab
+ location = "tabshifted";
+ }
+ Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
+ }],
+ /* Error */
+ [TorConnectState.Error, (self, errorMessage, errorDetails, fatal) => {
+ self._errorMessage = errorMessage;
+ self._errorDetails = errorDetails;
+
+ Services.obs.notifyObservers({message: errorMessage, details: errorDetails}, TorConnectTopics.BootstrapError);
+ if (fatal) {
+ self.onFatalError();
+ } else {
+ self.beginConfigure();
+ }
+ }],
+ /* FatalError */
+ [TorConnectState.FatalError, (self) => {
+ Services.obs.notifyObservers(null, TorConnectTopics.FatalError);
+ }],
+ /* Disabled */
+ [TorConnectState.Disabled, (self) => {
+
+ }],
+ ])),
+
+ _changeState: function(newState, ...args) {
+ const oldState = this._state;
+
+ // ensure this is a valid state transition
+ if (!TorConnectStateTransitions.get(oldState)?.includes(newState)) {
+ throw Error(`TorConnect: Attempted invalid state transition from ${oldState} to ${newState}`);
}
+
+ console.log(`TorConnect: transitioning state from ${oldState} to ${newState}`);
+
+ // call our transition function and forward any args
+ this._transitionCallbacks.get(newState)(this, ...args);
+
+ // finally, set our new state
+ this._state = newState;
+
+ Services.obs.notifyObservers({state: newState}, TorConnectTopics.StateChange);
+ },
+
+ // init should be called on app-startup in MainProcessingSingleton.jsm
+ init : function() {
+ console.log("TorConnect: Init");
+
+ // delay remaining init until after profile-after-change
+ Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
},
observe: function(subject, topic, data) {
+ console.log(`TorConnect: observed ${topic}`);
+
switch(topic) {
- case "TorBootstrapStatus":
- const obj = subject?.wrappedJSObject;
- if (obj?.PROGRESS === 100) {
- // open home page(s) in new tabs
- const win = BrowserWindowTracker.getTopWindow()
- const urls = Services.prefs.getStringPref("browser.startup.homepage").split('|');
-
- let location="tab";
- for(const url of urls) {
- win.openTrustedLinkIn(url, location);
- // open subsequent tabs behind first tab
- location = "tabshifted";
+
+ /* Determine which state to move to from Initial */
+ case BrowserTopics.ProfileAfterChange: {
+ if (TorLauncherUtil.useLegacyLauncher || !TorProtocolService.ownsTorDaemon) {
+ // Disabled
+ this.legacyOrSystemTor();
+ } else {
+ // register the Tor topics we always care about
+ for (const topicKey in TorTopics) {
+ const topic = TorTopics[topicKey];
+ Services.obs.addObserver(this, topic);
+ console.log(`TorConnect: observing topic '${topic}'`);
}
- Services.obs.notifyObservers(null, "torconnect:bootstrap-complete");
+ if (TorProtocolService.torProcessStatus == TorProcessStatus.Running) {
+ if (this.shouldQuickStart) {
+ // Quickstart
+ this.beginBootstrap();
+ } else {
+ // Configuring
+ this.beginConfigure();
+ }
+ }
}
+
+ Services.obs.removeObserver(this, topic);
break;
+ }
+ /* Transition out of Initial if Tor daemon wasn't running yet in BrowserTopics.ProfileAfterChange */
+ case TorTopics.ProcessIsReady: {
+ if (this.state === TorConnectState.Initial)
+ {
+ if (this.shouldQuickStart) {
+ // Quickstart
+ this.beginBootstrap();
+ } else {
+ // Configuring
+ this.beginConfigure();
+ }
+ }
+ break;
+ }
+ /* Updates our bootstrap status */
+ case TorTopics.BootstrapStatus: {
+ if (this._state != TorConnectState.Bootstrapping) {
+ console.log(`TorConnect: observed ${TorTopics.BootstrapStatus} topic while in state TorConnectState.${this._state}`);
+ break;
+ }
+
+ const obj = subject?.wrappedJSObject;
+ if (obj) {
+ this._bootstrapProgress= obj.PROGRESS;
+ this._bootstrapStatus = TorLauncherUtil.getLocalizedBootstrapStatus(obj, "TAG");
+
+ console.log(`TorConnect: Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`);
+ Services.obs.notifyObservers({
+ progress: this._bootstrapProgress,
+ status: this._bootstrapStatus,
+ hasWarnings: this._logHasWarningOrError
+ }, TorConnectTopics.BootstrapProgress);
+
+ if (this._bootstrapProgress === 100) {
+ this.bootstrapComplete();
+ }
+ }
+ break;
+ }
+ /* Handle bootstrap error*/
+ case TorTopics.BootstrapError: {
+ const obj = subject?.wrappedJSObject;
+ TorProtocolService.torStopBootstrap();
+ this.onError(obj.message, obj.details);
+ break;
+ }
+ case TorTopics.LogHasWarnOrErr: {
+ this._logHasWarningOrError = true;
+ break;
+ }
default:
// ignore
break;
}
},
- shouldShowTorConnect : function() {
- return TorProtocolService.shouldShowTorConnect();
+ /*
+ Various getters
+ */
+
+ get shouldShowTorConnect() {
+ // TorBrowser must control the daemon
+ return (TorProtocolService.ownsTorDaemon &&
+ // and we're not using the legacy launcher
+ !TorLauncherUtil.useLegacyLauncher &&
+ // legacy checks, TODO: maybe this should be in terms of our own state?
+ (TorProtocolService.isNetworkDisabled() || !TorProtocolService.isBootstrapDone()));
+ },
+
+ get shouldQuickStart() {
+ // quickstart must be enabled
+ return Services.prefs.getBoolPref(TorLauncherPrefs.quickstart, false) &&
+ // and the previous bootstrap attempt must have succeeded
+ !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true);
+ },
+
+ get state() {
+ return this._state;
+ },
+
+ get bootstrapProgress() {
+ return this._bootstrapProgress;
+ },
+
+ get bootstrapStatus() {
+ return this._bootstrapStatus;
+ },
+
+ get errorMessage() {
+ return this._errorMessage;
+ },
+
+ get errorDetails() {
+ return this._errorDetails;
+ },
+
+ get logHasWarningOrError() {
+ return this._logHasWarningOrError;
+ },
+
+ /*
+ These functions tell TorConnect to transition states
+ */
+
+ legacyOrSystemTor: function() {
+ console.log("TorConnect: legacyOrSystemTor()");
+ this._changeState(TorConnectState.Disabled);
+ },
+
+ beginBootstrap: function() {
+ console.log("TorConnect: beginBootstrap()");
+ this._changeState(TorConnectState.Bootstrapping);
+ },
+
+ beginConfigure: function() {
+ console.log("TorConnect: beginConfigure()");
+ this._changeState(TorConnectState.Configuring);
+ },
+
+ autoConfigure: function() {
+ console.log("TorConnect: autoConfigure()");
+ // TODO: implement
+ throw Error("TorConnect: not implemented");
+ },
+
+ cancelAutoConfigure: function() {
+ console.log("TorConnect: cancelAutoConfigure()");
+ // TODO: implement
+ throw Error("TorConnect: not implemented");
+ },
+
+ cancelBootstrap: function() {
+ console.log("TorConnect: cancelBootstrap()");
+ this._changeState(TorConnectState.Configuring);
+ },
+
+ bootstrapComplete: function() {
+ console.log("TorConnect: bootstrapComplete()");
+ this._changeState(TorConnectState.Bootstrapped);
+ },
+
+ onError: function(message, details) {
+ console.log("TorConnect: onError()");
+ this._changeState(TorConnectState.Error, message, details, false);
+ },
+
+ onFatalError: function() {
+ console.log("TorConnect: onFatalError()");
+ // TODO: implement
+ throw Error("TorConnect: not implemented");
+ },
+
+ /*
+ Further external commands and helper methods
+ */
+ openTorPreferences: function() {
+ const win = BrowserWindowTracker.getTopWindow()
+ win.openTrustedLinkIn("about:preferences#tor", "tab");
+ },
+
+ copyTorLogs: function() {
+ // Copy tor log messages to the system clipboard.
+ const chSvc = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+ Ci.nsIClipboardHelper
+ );
+ const countObj = { value: 0 };
+ chSvc.copyString(TorProtocolService.getLog(countObj));
+ const count = countObj.value;
+ return TorLauncherUtil.getFormattedLocalizedString(
+ "copiedNLogMessagesShort",
+ [count],
+ 1
+ );
+ },
+
+ // called from browser.js on browser startup, passed in either the user's homepage(s)
+ // or uris passed via command-line
+ setURIsToLoad: function(uriVariant) {
+ // convert the object we get from browser.js
+ let uris = ((v) => {
+ if (v instanceof Ci.nsIArray) {
+ // Transform the nsIArray of nsISupportsString's into a JS Array of
+ // JS strings.
+ return Array.from(
+ v.enumerate(Ci.nsISupportsString),
+ supportStr => supportStr.data
+ );
+ } else if (v instanceof Ci.nsISupportsString) {
+ return [v.data];
+ } else if (typeof v === "string") {
+ return v.split("|");
+ }
+ // about:tor as safe fallback
+ return ["about:tor"];
+ })(uriVariant);
+
+ console.log(`TorConnect: will load after bootstrap => ${uris.join(", ")}`);
+ this._urisToLoad = uris;
},
};
retval.init();
return retval;
-})(); /* TorConnect */
\ No newline at end of file
+})(); /* TorConnect */
diff --git a/browser/modules/TorProtocolService.jsm b/browser/modules/TorProtocolService.jsm
index fc7f2c884aa2..e6c78b9a0eb1 100644
--- a/browser/modules/TorProtocolService.jsm
+++ b/browser/modules/TorProtocolService.jsm
@@ -2,26 +2,59 @@
"use strict";
-var EXPORTED_SYMBOLS = ["TorProtocolService"];
+var EXPORTED_SYMBOLS = ["TorProtocolService", "TorProcessStatus"];
-const { TorLauncherUtil } = ChromeUtils.import(
- "resource://torlauncher/modules/tl-util.jsm"
+const { Services } = ChromeUtils.import(
+ "resource://gre/modules/Services.jsm"
);
-var TorProtocolService = {
- _tlps: Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
- Ci.nsISupports
- ).wrappedJSObject,
+// see tl-process.js
+const TorProcessStatus = Object.freeze({
+ Unknown: 0,
+ Starting: 1,
+ Running: 2,
+ Exited: 3,
+});
+
+/* Browser observer topis */
+const BrowserTopics = Object.freeze({
+ ProfileAfterChange: "profile-after-change",
+});
- _tlproc: Cc["@torproject.org/torlauncher-process-service;1"].getService(
- Ci.nsISupports
- ).wrappedJSObject,
+var TorProtocolService = {
+ _TorLauncherUtil: function() {
+ let { TorLauncherUtil } = ChromeUtils.import(
+ "resource://torlauncher/modules/tl-util.jsm"
+ );
+ return TorLauncherUtil;
+ }(),
+ _TorLauncherProtocolService: null,
+ _TorProcessService: null,
// maintain a map of tor settings set by Tor Browser so that we don't
// repeatedly set the same key/values over and over
// this map contains string keys to primitive or array values
_settingsCache: new Map(),
+ init() {
+ Services.obs.addObserver(this, BrowserTopics.ProfileAfterChange);
+ },
+
+ observe(subject, topic, data) {
+ if (topic === BrowserTopics.ProfileAfterChange) {
+ // we have to delay init'ing this or else the crypto service inits too early without a profile
+ // which breaks the password manager
+ this._TorLauncherProtocolService = Cc["@torproject.org/torlauncher-protocol-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject;
+ this._TorProcessService = Cc["@torproject.org/torlauncher-process-service;1"].getService(
+ Ci.nsISupports
+ ).wrappedJSObject,
+
+ Services.obs.removeObserver(this, topic);
+ }
+ },
+
_typeof(aValue) {
switch (typeof aValue) {
case "boolean":
@@ -124,7 +157,7 @@ var TorProtocolService = {
}
let errorObject = {};
- if (!this._tlps.TorSetConfWithReply(settingsObject, errorObject)) {
+ if (!this._TorLauncherProtocolService.TorSetConfWithReply(settingsObject, errorObject)) {
throw new Error(errorObject.details);
}
@@ -137,8 +170,8 @@ var TorProtocolService = {
_readSetting(aSetting) {
this._assertValidSettingKey(aSetting);
- let reply = this._tlps.TorGetConf(aSetting);
- if (this._tlps.TorCommandSucceeded(reply)) {
+ let reply = this._TorLauncherProtocolService.TorGetConf(aSetting);
+ if (this._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
return reply.lineArray;
}
throw new Error(reply.lineArray.join("\n"));
@@ -207,22 +240,22 @@ var TorProtocolService = {
getLog(countObj) {
countObj = countObj || { value: 0 };
- let torLog = this._tlps.TorGetLog(countObj);
+ let torLog = this._TorLauncherProtocolService.TorGetLog(countObj);
return torLog;
},
// true if we launched and control tor, false if using system tor
get ownsTorDaemon() {
- return TorLauncherUtil.shouldStartAndOwnTor;
+ return this._TorLauncherUtil.shouldStartAndOwnTor;
},
// Assumes `ownsTorDaemon` is true
isNetworkDisabled() {
- const reply = TorProtocolService._tlps.TorGetConfBool(
+ const reply = TorProtocolService._TorLauncherProtocolService.TorGetConfBool(
"DisableNetwork",
true
);
- if (TorProtocolService._tlps.TorCommandSucceeded(reply)) {
+ if (TorProtocolService._TorLauncherProtocolService.TorCommandSucceeded(reply)) {
return reply.retVal;
}
return true;
@@ -232,22 +265,22 @@ var TorProtocolService = {
let settings = {};
settings.DisableNetwork = false;
let errorObject = {};
- if (!this._tlps.TorSetConfWithReply(settings, errorObject)) {
+ if (!this._TorLauncherProtocolService.TorSetConfWithReply(settings, errorObject)) {
throw new Error(errorObject.details);
}
},
sendCommand(cmd) {
- return this._tlps.TorSendCommand(cmd);
+ return this._TorLauncherProtocolService.TorSendCommand(cmd);
},
retrieveBootstrapStatus() {
- return this._tlps.TorRetrieveBootstrapStatus();
+ return this._TorLauncherProtocolService.TorRetrieveBootstrapStatus();
},
_GetSaveSettingsErrorMessage(aDetails) {
try {
- return TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
+ return this._TorLauncherUtil.getSaveSettingsErrorMessage(aDetails);
} catch (e) {
console.log("GetSaveSettingsErrorMessage error", e);
return "Unexpected Error";
@@ -258,7 +291,7 @@ var TorProtocolService = {
let result = false;
const error = {};
try {
- result = this._tlps.TorSetConfWithReply(settings, error);
+ result = this._TorLauncherProtocolService.TorSetConfWithReply(settings, error);
} catch (e) {
console.log("TorSetConfWithReply error", e);
error.details = this._GetSaveSettingsErrorMessage(e.message);
@@ -267,23 +300,15 @@ var TorProtocolService = {
},
isBootstrapDone() {
- return this._tlproc.mIsBootstrapDone;
+ return this._TorProcessService.mIsBootstrapDone;
},
clearBootstrapError() {
- return this._tlproc.TorClearBootstrapError();
- },
-
- shouldShowTorConnect() {
- return (
- this.ownsTorDaemon &&
- !TorLauncherUtil.useLegacyLauncher &&
- (this.isNetworkDisabled() || !this.isBootstrapDone())
- );
+ return this._TorProcessService.TorClearBootstrapError();
},
torBootstrapErrorOccurred() {
- return this._tlproc.TorBootstrapErrorOccurred;
+ return this._TorProcessService.TorBootstrapErrorOccurred;
},
// Resolves to null if ok, or an error otherwise
@@ -306,7 +331,7 @@ var TorProtocolService = {
},
torLogHasWarnOrErr() {
- return this._tlps.TorLogHasWarnOrErr;
+ return this._TorLauncherProtocolService.TorLogHasWarnOrErr;
},
torStopBootstrap() {
@@ -327,4 +352,12 @@ var TorProtocolService = {
}
this.retrieveBootstrapStatus();
},
+
+ get torProcessStatus() {
+ if (this._TorProcessService) {
+ return this._TorProcessService.TorProcessStatus;
+ }
+ return TorProcessStatus.Unknown;
+ },
};
+TorProtocolService.init();
\ No newline at end of file
diff --git a/toolkit/components/processsingleton/MainProcessSingleton.jsm b/toolkit/components/processsingleton/MainProcessSingleton.jsm
index db1e2dc8f568..ea9288dccbb3 100644
--- a/toolkit/components/processsingleton/MainProcessSingleton.jsm
+++ b/toolkit/components/processsingleton/MainProcessSingleton.jsm
@@ -24,6 +24,11 @@ MainProcessSingleton.prototype = {
null
);
+ ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm",
+ null
+ );
+
// Load this script early so that console.* is initialized
// before other frame scripts.
Services.mm.loadFrameScript(
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 0927391c2ba7..54230e1175ec 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -181,28 +181,18 @@ let RemotePageAccessManager = {
RPMRemoveMessageListener: ["*"],
},
"about:torconnect": {
- RPMAddMessageListener: ["*"],
+ RPMAddMessageListener: [
+ "torconnect:state-change",
+ ],
RPMSendAsyncMessage: [
- "OpenTorAdvancedPreferences",
- "TorRetrieveBootstrapStatus",
- "TorStopBootstrap",
+ "torconnect:open-tor-preferences",
+ "torconnect:begin-bootstrap",
+ "torconnect:cancel-bootstrap",
+ "torconnect:set-quickstart",
],
RPMSendQuery: [
- "GetDirection",
- "GetLocalizedBootstrapStatus",
- "GetTorStrings",
- "TorBootstrapErrorOccurred",
- "TorConnect",
- "TorCopyLog",
- "TorIsNetworkDisabled",
- "TorLogHasWarnOrErr",
- ],
- RPMGetBoolPref: [
- "extensions.torlauncher.quickstart",
- "extensions.torlauncher.prompt_at_startup",
- ],
- RPMSetBoolPref: [
- "extensions.torlauncher.quickstart",
+ "torconnect:get-init-args",
+ "torconnect:copy-tor-logs",
],
},
},
More information about the tbb-commits
mailing list