henry pushed to branch tor-browser-150.0a1-16.0-2 at The Tor Project / Applications / Tor Browser
Commits:
-
b61277be
by Henry Wilkes at 2026-04-28T12:46:37+01:00
-
6a41ffd8
by Henry Wilkes at 2026-04-28T12:53:36+01:00
-
7167229f
by Henry Wilkes at 2026-04-28T12:53:37+01:00
-
adba934e
by Henry Wilkes at 2026-04-28T12:53:39+01:00
-
e74c8d84
by Henry Wilkes at 2026-04-28T12:53:40+01:00
-
615707ff
by Henry Wilkes at 2026-04-28T12:53:41+01:00
-
48f5bfb2
by Henry Wilkes at 2026-04-28T12:53:42+01:00
-
424f8e1a
by Henry Wilkes at 2026-04-28T12:53:43+01:00
8 changed files:
- toolkit/components/tor-launcher/TorControlPort.sys.mjs
- toolkit/components/tor-launcher/TorProvider.sys.mjs
- + toolkit/components/tor-launcher/TorProviderBase.sys.mjs
- toolkit/components/tor-launcher/TorProviderBuilder.sys.mjs
- + toolkit/components/tor-launcher/TorProviderNone.sys.mjs
- toolkit/components/tor-launcher/moz.build
- toolkit/modules/TorConnect.sys.mjs
- toolkit/modules/TorSettings.sys.mjs
Changes:
| ... | ... | @@ -384,21 +384,19 @@ export class TorController { |
| 384 | 384 | /**
|
| 385 | 385 | * The event handler.
|
| 386 | 386 | *
|
| 387 | - * @type {TorEventHandler}
|
|
| 387 | + * @type {?TorEventHandler}
|
|
| 388 | 388 | */
|
| 389 | - #eventHandler;
|
|
| 389 | + #eventHandler = null;
|
|
| 390 | 390 | |
| 391 | 391 | /**
|
| 392 | 392 | * Connect to a control port over a Unix socket.
|
| 393 | 393 | * Not available on Windows.
|
| 394 | 394 | *
|
| 395 | 395 | * @param {nsIFile} ipcFile The path to the Unix socket to connect to
|
| 396 | - * @param {TorEventHandler} eventHandler The event handler to use for
|
|
| 397 | - * asynchronous notifications
|
|
| 398 | 396 | * @returns {TorController}
|
| 399 | 397 | */
|
| 400 | - static fromIpcFile(ipcFile, eventHandler) {
|
|
| 401 | - return new TorController(AsyncSocket.fromIpcFile(ipcFile), eventHandler);
|
|
| 398 | + static fromIpcFile(ipcFile) {
|
|
| 399 | + return new TorController(AsyncSocket.fromIpcFile(ipcFile));
|
|
| 402 | 400 | }
|
| 403 | 401 | |
| 404 | 402 | /**
|
| ... | ... | @@ -406,15 +404,10 @@ export class TorController { |
| 406 | 404 | *
|
| 407 | 405 | * @param {string} host The hostname to connect to
|
| 408 | 406 | * @param {number} port The port to connect the to
|
| 409 | - * @param {TorEventHandler} eventHandler The event handler to use for
|
|
| 410 | - * asynchronous notifications
|
|
| 411 | 407 | * @returns {TorController}
|
| 412 | 408 | */
|
| 413 | - static fromSocketAddress(host, port, eventHandler) {
|
|
| 414 | - return new TorController(
|
|
| 415 | - AsyncSocket.fromSocketAddress(host, port),
|
|
| 416 | - eventHandler
|
|
| 417 | - );
|
|
| 409 | + static fromSocketAddress(host, port) {
|
|
| 410 | + return new TorController(AsyncSocket.fromSocketAddress(host, port));
|
|
| 418 | 411 | }
|
| 419 | 412 | |
| 420 | 413 | /**
|
| ... | ... | @@ -425,15 +418,22 @@ export class TorController { |
| 425 | 418 | *
|
| 426 | 419 | * @private
|
| 427 | 420 | * @param {AsyncSocket} socket The socket to use
|
| 428 | - * @param {TorEventHandler} eventHandler The event handler to use for
|
|
| 429 | - * asynchronous notifications
|
|
| 430 | 421 | */
|
| 431 | - constructor(socket, eventHandler) {
|
|
| 422 | + constructor(socket) {
|
|
| 432 | 423 | this.#socket = socket;
|
| 433 | - this.#eventHandler = eventHandler;
|
|
| 434 | 424 | this.#startMessagePump();
|
| 435 | 425 | }
|
| 436 | 426 | |
| 427 | + /**
|
|
| 428 | + * Set an event handler for this instance.
|
|
| 429 | + *
|
|
| 430 | + * @param {TorEventHandler} eventHandler The event handler to use for
|
|
| 431 | + * asynchronous notifications
|
|
| 432 | + */
|
|
| 433 | + setEventHandler(eventHandler) {
|
|
| 434 | + this.#eventHandler = eventHandler;
|
|
| 435 | + }
|
|
| 436 | + |
|
| 437 | 437 | // Socket and communication handling
|
| 438 | 438 | |
| 439 | 439 | /**
|
| ... | ... | @@ -649,6 +649,7 @@ export class TorController { |
| 649 | 649 | this.#socket?.close();
|
| 650 | 650 | } finally {
|
| 651 | 651 | this.#socket = null;
|
| 652 | + this.#eventHandler?.onClosed();
|
|
| 652 | 653 | }
|
| 653 | 654 | }
|
| 654 | 655 | |
| ... | ... | @@ -1324,6 +1325,7 @@ export class TorController { |
| 1324 | 1325 | * The controller owner can implement this methods to receive asynchronous
|
| 1325 | 1326 | * notifications from the controller.
|
| 1326 | 1327 | *
|
| 1328 | + * @property {OnClosed} onClosed Called when the socket is closed.
|
|
| 1327 | 1329 | * @property {OnBootstrapStatus} onBootstrapStatus Called when a bootstrap
|
| 1328 | 1330 | * status is received (i.e., a STATUS_CLIENT event with a BOOTSTRAP action)
|
| 1329 | 1331 | * @property {OnLogMessage} onLogMessage Called when a log message is received
|
| ... | ... | @@ -1336,6 +1338,9 @@ export class TorController { |
| 1336 | 1338 | * a connect cell along a circuit (i.e., a STREAM event with a SENTCONNECT
|
| 1337 | 1339 | * status)
|
| 1338 | 1340 | */
|
| 1341 | +/**
|
|
| 1342 | + * @callback OnClosed
|
|
| 1343 | + */
|
|
| 1339 | 1344 | /**
|
| 1340 | 1345 | * @callback OnBootstrapStatus
|
| 1341 | 1346 | *
|
| ... | ... | @@ -6,6 +6,7 @@ import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs"; |
| 6 | 6 | |
| 7 | 7 | import { TorLauncherUtil } from "resource://gre/modules/TorLauncherUtil.sys.mjs";
|
| 8 | 8 | import { TorParsers } from "resource://gre/modules/TorParsers.sys.mjs";
|
| 9 | +import { TorProviderBase } from "resource://gre/modules/TorProviderBase.sys.mjs";
|
|
| 9 | 10 | import {
|
| 10 | 11 | TorBootstrapError,
|
| 11 | 12 | TorProviderTopics,
|
| ... | ... | @@ -88,7 +89,7 @@ const TorConfigKeys = Object.freeze({ |
| 88 | 89 | * It can start a new tor instance, or connect to an existing one.
|
| 89 | 90 | * In the former case, it also takes its ownership by default.
|
| 90 | 91 | */
|
| 91 | -export class TorProvider {
|
|
| 92 | +export class TorProvider extends TorProviderBase {
|
|
| 92 | 93 | /**
|
| 93 | 94 | * The control port settings.
|
| 94 | 95 | *
|
| ... | ... | @@ -184,7 +185,7 @@ export class TorProvider { |
| 184 | 185 | * Starts a new tor process and connect to its control port, or connect to the
|
| 185 | 186 | * control port of an existing tor daemon.
|
| 186 | 187 | */
|
| 187 | - async init() {
|
|
| 188 | + async _initInternal() {
|
|
| 188 | 189 | logger.debug("Initializing the Tor provider.");
|
| 189 | 190 | |
| 190 | 191 | // These settings might be customized in the following steps.
|
| ... | ... | @@ -259,7 +260,7 @@ export class TorProvider { |
| 259 | 260 | * control connection is closed. Therefore, as a matter of facts, calling this
|
| 260 | 261 | * function also makes the child Tor instance stop.
|
| 261 | 262 | */
|
| 262 | - uninit() {
|
|
| 263 | + async _uninitInternal() {
|
|
| 263 | 264 | logger.debug("Uninitializing the Tor provider.");
|
| 264 | 265 | |
| 265 | 266 | if (this.#torProcess) {
|
| ... | ... | @@ -509,17 +510,6 @@ export class TorProvider { |
| 509 | 510 | return TorLauncherUtil.shouldStartAndOwnTor;
|
| 510 | 511 | }
|
| 511 | 512 | |
| 512 | - /**
|
|
| 513 | - * TODO: Rename to isReady once we remove finish the migration.
|
|
| 514 | - *
|
|
| 515 | - * @returns {boolean} true if we currently have a connection to the control
|
|
| 516 | - * port. We take for granted that if we have one, we authenticated to it, and
|
|
| 517 | - * so we have already verified we can send and receive data.
|
|
| 518 | - */
|
|
| 519 | - get isRunning() {
|
|
| 520 | - return this.#controlConnection?.isOpen ?? false;
|
|
| 521 | - }
|
|
| 522 | - |
|
| 523 | 513 | /**
|
| 524 | 514 | * Return the data about the current bridge, if any, or null.
|
| 525 | 515 | * We can detect bridge only when the configured bridge lines include the
|
| ... | ... | @@ -740,6 +730,11 @@ export class TorProvider { |
| 740 | 730 | };
|
| 741 | 731 | tryConnect();
|
| 742 | 732 | });
|
| 733 | + // The previous TorControlPort instances may have failed, in which case we
|
|
| 734 | + // want to ignore their events (such as `onClose`). This instance has just
|
|
| 735 | + // succeeded and will be owned by this TorProvider instance, therefore we
|
|
| 736 | + // want to start listening to its events now.
|
|
| 737 | + this.#controlConnection.setEventHandler(this);
|
|
| 743 | 738 | |
| 744 | 739 | // The following code will never throw, but we still want to wait for it
|
| 745 | 740 | // before marking the provider as initialized.
|
| ... | ... | @@ -751,7 +746,6 @@ export class TorProvider { |
| 751 | 746 | this.#torProcess.onExit = exitCode => {
|
| 752 | 747 | logger.info(`The tor process exited with code ${exitCode}`);
|
| 753 | 748 | this.#closeConnection("The tor process exited suddenly");
|
| 754 | - Services.obs.notifyObservers(null, TorProviderTopics.ProcessExited);
|
|
| 755 | 749 | };
|
| 756 | 750 | if (!TorLauncherUtil.shouldOnlyConfigureTor) {
|
| 757 | 751 | await this.#takeOwnership();
|
| ... | ... | @@ -823,14 +817,12 @@ export class TorProvider { |
| 823 | 817 | let controlPort;
|
| 824 | 818 | if (this.#controlPortSettings.ipcFile) {
|
| 825 | 819 | controlPort = lazy.TorController.fromIpcFile(
|
| 826 | - this.#controlPortSettings.ipcFile,
|
|
| 827 | - this
|
|
| 820 | + this.#controlPortSettings.ipcFile
|
|
| 828 | 821 | );
|
| 829 | 822 | } else {
|
| 830 | 823 | controlPort = lazy.TorController.fromSocketAddress(
|
| 831 | 824 | this.#controlPortSettings.host,
|
| 832 | - this.#controlPortSettings.port,
|
|
| 833 | - this
|
|
| 825 | + this.#controlPortSettings.port
|
|
| 834 | 826 | );
|
| 835 | 827 | }
|
| 836 | 828 | try {
|
| ... | ... | @@ -864,21 +856,27 @@ export class TorProvider { |
| 864 | 856 | * attempt)
|
| 865 | 857 | */
|
| 866 | 858 | #closeConnection(reason) {
|
| 867 | - this.#cancelConnection(reason);
|
|
| 868 | - if (this.#controlConnection) {
|
|
| 869 | - logger.info("Closing the control connection", reason);
|
|
| 870 | - try {
|
|
| 871 | - this.#controlConnection.close();
|
|
| 872 | - } catch (e) {
|
|
| 873 | - logger.error("Failed to close the control port connection", e);
|
|
| 859 | + try {
|
|
| 860 | + this.#cancelConnection(reason);
|
|
| 861 | + if (this.#controlConnection) {
|
|
| 862 | + logger.info("Closing the control connection", reason);
|
|
| 863 | + try {
|
|
| 864 | + this.#controlConnection.close();
|
|
| 865 | + } catch (e) {
|
|
| 866 | + logger.error("Failed to close the control port connection", e);
|
|
| 867 | + }
|
|
| 868 | + this.#controlConnection = null;
|
|
| 869 | + } else {
|
|
| 870 | + logger.trace(
|
|
| 871 | + "Requested to close an already closed control port connection"
|
|
| 872 | + );
|
|
| 874 | 873 | }
|
| 875 | - this.#controlConnection = null;
|
|
| 876 | - } else {
|
|
| 877 | - logger.trace(
|
|
| 878 | - "Requested to close an already closed control port connection"
|
|
| 879 | - );
|
|
| 874 | + } finally {
|
|
| 875 | + this.#lastWarning = {};
|
|
| 876 | + // #controlConnection.close() may already trigger onClosed, but we
|
|
| 877 | + // call it once more for error cases as well.
|
|
| 878 | + this._stoppedInternal();
|
|
| 880 | 879 | }
|
| 881 | - this.#lastWarning = {};
|
|
| 882 | 880 | }
|
| 883 | 881 | |
| 884 | 882 | // Authentication
|
| ... | ... | @@ -975,6 +973,13 @@ export class TorProvider { |
| 975 | 973 | |
| 976 | 974 | // Notification handlers
|
| 977 | 975 | |
| 976 | + /**
|
|
| 977 | + * Notification that the control port has closed.
|
|
| 978 | + */
|
|
| 979 | + onClosed() {
|
|
| 980 | + this._stoppedInternal();
|
|
| 981 | + }
|
|
| 982 | + |
|
| 978 | 983 | /**
|
| 979 | 984 | * Receive and process a notification with the bootstrap status.
|
| 980 | 985 | *
|
| 1 | +import {
|
|
| 2 | + TorProviderState,
|
|
| 3 | + TorProviderInitError,
|
|
| 4 | +} from "resource://gre/modules/TorProviderBuilder.sys.mjs";
|
|
| 5 | + |
|
| 6 | +/**
|
|
| 7 | + * @callback StateChangedCallback
|
|
| 8 | + */
|
|
| 9 | + |
|
| 10 | +/**
|
|
| 11 | + * The base class for tor providers.
|
|
| 12 | + */
|
|
| 13 | +export class TorProviderBase {
|
|
| 14 | + /**
|
|
| 15 | + * The current provider state.
|
|
| 16 | + *
|
|
| 17 | + * @type {string}
|
|
| 18 | + */
|
|
| 19 | + #state = TorProviderState.Starting;
|
|
| 20 | + |
|
| 21 | + /**
|
|
| 22 | + * The callback for when our state changes. `null` once the `uninit` method is
|
|
| 23 | + * called.
|
|
| 24 | + *
|
|
| 25 | + * @type {?StateChangedCallback}
|
|
| 26 | + */
|
|
| 27 | + #stateChangedCallback;
|
|
| 28 | + |
|
| 29 | + /**
|
|
| 30 | + * The promise to return from the `init` method.
|
|
| 31 | + *
|
|
| 32 | + * @type {?Promise<undefined>}
|
|
| 33 | + */
|
|
| 34 | + #initPromise = null;
|
|
| 35 | + |
|
| 36 | + /**
|
|
| 37 | + * The promise to return from the `uninit` method.
|
|
| 38 | + *
|
|
| 39 | + * @type {?Promise<undefined>}
|
|
| 40 | + */
|
|
| 41 | + #uninitPromise = null;
|
|
| 42 | + |
|
| 43 | + /**
|
|
| 44 | + * Create a new provider.
|
|
| 45 | + *
|
|
| 46 | + * @param {StateChangedCallback} stateChangedCallback - A callback to let an
|
|
| 47 | + * owner know that a provider's state has changed.
|
|
| 48 | + */
|
|
| 49 | + // NOTE: This should *not* be overridden by implementations.
|
|
| 50 | + constructor(stateChangedCallback) {
|
|
| 51 | + this.#stateChangedCallback = stateChangedCallback;
|
|
| 52 | + }
|
|
| 53 | + |
|
| 54 | + /**
|
|
| 55 | + * Initialize the provider and wait for it to be `Running`.
|
|
| 56 | + *
|
|
| 57 | + * This is safe to call multiple times, with each call waiting for the
|
|
| 58 | + * provider to be ready.
|
|
| 59 | + */
|
|
| 60 | + // NOTE: This should *not* be overridden by implementations.
|
|
| 61 | + async init() {
|
|
| 62 | + if (!this.#initPromise) {
|
|
| 63 | + this.#initPromise = this._initInternal().then(
|
|
| 64 | + () => {
|
|
| 65 | + this.#setState(TorProviderState.Running);
|
|
| 66 | + },
|
|
| 67 | + error => {
|
|
| 68 | + this.#setState(TorProviderState.Stopped);
|
|
| 69 | + // Wrap the error in `TorProviderInitError` to let callers know that
|
|
| 70 | + // this error is an initialization error.
|
|
| 71 | + throw new TorProviderInitError(error);
|
|
| 72 | + }
|
|
| 73 | + );
|
|
| 74 | + }
|
|
| 75 | + return this.#initPromise;
|
|
| 76 | + }
|
|
| 77 | + |
|
| 78 | + /**
|
|
| 79 | + * Uninitialize the provider and wait for it to be cleaned up.
|
|
| 80 | + *
|
|
| 81 | + * This is safe to call multiple times, with each call waiting for the
|
|
| 82 | + * clean up.
|
|
| 83 | + */
|
|
| 84 | + // NOTE: This should *not* be overridden by implementations.
|
|
| 85 | + async uninit() {
|
|
| 86 | + if (!this.#uninitPromise) {
|
|
| 87 | + this.#setState(TorProviderState.Stopped);
|
|
| 88 | + this.#stateChangedCallback = null;
|
|
| 89 | + this.#uninitPromise = this._uninitInternal();
|
|
| 90 | + }
|
|
| 91 | + return this.#uninitPromise;
|
|
| 92 | + }
|
|
| 93 | + |
|
| 94 | + /**
|
|
| 95 | + * The current `TorProviderState` state of the provider.
|
|
| 96 | + *
|
|
| 97 | + * @type {string}
|
|
| 98 | + */
|
|
| 99 | + // NOTE: This should *not* be overridden by implementations.
|
|
| 100 | + get state() {
|
|
| 101 | + return this.#state;
|
|
| 102 | + }
|
|
| 103 | + |
|
| 104 | + /**
|
|
| 105 | + * Set the state of the provider. Announcing this via a callback.
|
|
| 106 | + *
|
|
| 107 | + * @param {string} state - The new `TorProviderState`.
|
|
| 108 | + */
|
|
| 109 | + #setState(state) {
|
|
| 110 | + if (state === this.#state) {
|
|
| 111 | + return;
|
|
| 112 | + }
|
|
| 113 | + if (this.#state === TorProviderState.Stopped) {
|
|
| 114 | + // Ignore any changes away from the `Stopped` state, which is unexpected
|
|
| 115 | + // since implementations can only trigger the `Stopped` state, and
|
|
| 116 | + // everything else is handled by `TorProviderBase`.
|
|
| 117 | + return;
|
|
| 118 | + }
|
|
| 119 | + this.#state = state;
|
|
| 120 | + this.#stateChangedCallback?.();
|
|
| 121 | + }
|
|
| 122 | + |
|
| 123 | + /**
|
|
| 124 | + * An internal method to be called by implementations when they stop working.
|
|
| 125 | + *
|
|
| 126 | + * Optional and safe to call as part of `_uninitInternal`.
|
|
| 127 | + */
|
|
| 128 | + // NOTE: This should *not* be overridden by implementations.
|
|
| 129 | + _stoppedInternal() {
|
|
| 130 | + this.#setState(TorProviderState.Stopped);
|
|
| 131 | + }
|
|
| 132 | + |
|
| 133 | + // Implementation methods.
|
|
| 134 | + |
|
| 135 | + /**
|
|
| 136 | + * An internal initialization method for provider instances to implement.
|
|
| 137 | + */
|
|
| 138 | + async _initInternal() {
|
|
| 139 | + throw new Error("_initInternal not implemented.");
|
|
| 140 | + }
|
|
| 141 | + |
|
| 142 | + /**
|
|
| 143 | + * An internal uninitialization method for provider instances to implement.
|
|
| 144 | + */
|
|
| 145 | + async _uninitInternal() {
|
|
| 146 | + throw new Error("_uninitInternal not implemented.");
|
|
| 147 | + }
|
|
| 148 | + |
|
| 149 | + async writeBridgeSettings(_bridges) {
|
|
| 150 | + throw new Error("writeBridgeSettings not implemented.");
|
|
| 151 | + }
|
|
| 152 | + |
|
| 153 | + async writeProxySettings(_proxy) {
|
|
| 154 | + throw new Error("writeProxySettings not implemented.");
|
|
| 155 | + }
|
|
| 156 | + |
|
| 157 | + async writeFirewallSettings(_firewall) {
|
|
| 158 | + throw new Error("writeFirewallSettings not implemented.");
|
|
| 159 | + }
|
|
| 160 | + |
|
| 161 | + async flushSettings() {
|
|
| 162 | + throw new Error("flushSettings not implemented.");
|
|
| 163 | + }
|
|
| 164 | + |
|
| 165 | + async connect() {
|
|
| 166 | + throw new Error("connect not implemented.");
|
|
| 167 | + }
|
|
| 168 | + |
|
| 169 | + async stopBootstrap() {
|
|
| 170 | + throw new Error("stopBootstrap not implemented.");
|
|
| 171 | + }
|
|
| 172 | + |
|
| 173 | + async newnym() {
|
|
| 174 | + throw new Error("newnym not implemented.");
|
|
| 175 | + }
|
|
| 176 | + |
|
| 177 | + async getBridges() {
|
|
| 178 | + throw new Error("getBridges not implemented.");
|
|
| 179 | + }
|
|
| 180 | + |
|
| 181 | + async getPluggableTransports() {
|
|
| 182 | + throw new Error("getPluggableTransports not implemented.");
|
|
| 183 | + }
|
|
| 184 | + |
|
| 185 | + async onionAuthAdd(_address, _b64PrivateKey, _isPermanent) {
|
|
| 186 | + throw new Error("onionAuthAdd not implemented.");
|
|
| 187 | + }
|
|
| 188 | + |
|
| 189 | + async onionAuthRemove(_address) {
|
|
| 190 | + throw new Error("onionAuthRemove not implemented.");
|
|
| 191 | + }
|
|
| 192 | + |
|
| 193 | + async onionAuthViewKeys() {
|
|
| 194 | + throw new Error("onionAuthViewKeys not implemented.");
|
|
| 195 | + }
|
|
| 196 | + |
|
| 197 | + get currentBridge() {
|
|
| 198 | + throw new Error("currentBridge not implemented.");
|
|
| 199 | + }
|
|
| 200 | +} |
| ... | ... | @@ -6,10 +6,19 @@ const lazy = {}; |
| 6 | 6 | ChromeUtils.defineESModuleGetters(lazy, {
|
| 7 | 7 | TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
| 8 | 8 | TorProvider: "resource://gre/modules/TorProvider.sys.mjs",
|
| 9 | + TorProviderNone: "resource://gre/modules/TorProviderNone.sys.mjs",
|
|
| 10 | +});
|
|
| 11 | + |
|
| 12 | +ChromeUtils.defineLazyGetter(lazy, "logger", () => {
|
|
| 13 | + return console.createInstance({
|
|
| 14 | + // Share preference with TorProvider.
|
|
| 15 | + maxLogLevelPref: "browser.tor_provider.log_level",
|
|
| 16 | + prefix: "TorProviderBuilder",
|
|
| 17 | + });
|
|
| 9 | 18 | });
|
| 10 | 19 | |
| 11 | 20 | export const TorProviderTopics = Object.freeze({
|
| 12 | - ProcessExited: "TorProcessExited",
|
|
| 21 | + ProviderStateChanged: "TorProviderStateChanged",
|
|
| 13 | 22 | BootstrapStatus: "TorBootstrapStatus",
|
| 14 | 23 | BootstrapError: "TorBootstrapError",
|
| 15 | 24 | TorLog: "TorLog",
|
| ... | ... | @@ -18,6 +27,15 @@ export const TorProviderTopics = Object.freeze({ |
| 18 | 27 | CircuitCredentialsMatched: "TorCircuitCredentialsMatched",
|
| 19 | 28 | });
|
| 20 | 29 | |
| 30 | +/**
|
|
| 31 | + * The tracked state a provider might be in.
|
|
| 32 | + */
|
|
| 33 | +export const TorProviderState = Object.freeze({
|
|
| 34 | + Starting: "Starting",
|
|
| 35 | + Running: "Running",
|
|
| 36 | + Stopped: "Stopped",
|
|
| 37 | +});
|
|
| 38 | + |
|
| 21 | 39 | /**
|
| 22 | 40 | * Wrapper error class for errors raised during TorProvider.init.
|
| 23 | 41 | */
|
| ... | ... | @@ -54,8 +72,8 @@ export class TorBootstrapError extends Error { |
| 54 | 72 | }
|
| 55 | 73 | |
| 56 | 74 | export const TorProviders = Object.freeze({
|
| 57 | - none: 0,
|
|
| 58 | - tor: 1,
|
|
| 75 | + none: "none",
|
|
| 76 | + tor: "tor",
|
|
| 59 | 77 | });
|
| 60 | 78 | |
| 61 | 79 | /**
|
| ... | ... | @@ -65,6 +83,17 @@ export const TorProviders = Object.freeze({ |
| 65 | 83 | * @property {string} msg The message
|
| 66 | 84 | */
|
| 67 | 85 | |
| 86 | +/**
|
|
| 87 | + * @typedef {object} TorProviderData
|
|
| 88 | + *
|
|
| 89 | + * The data associated with a tor provider.
|
|
| 90 | + *
|
|
| 91 | + * @property {TorProviderBase} provider - The provider instance.
|
|
| 92 | + * @property {Promise<undefined>} initPromise - A promise that fulfils after the
|
|
| 93 | + * previous provider is cleaned up and the new provider's initialization
|
|
| 94 | + * completes or throws an error.
|
|
| 95 | + */
|
|
| 96 | + |
|
| 68 | 97 | /**
|
| 69 | 98 | * The factory to get a Tor provider.
|
| 70 | 99 | * Currently we support only TorProvider, i.e., the one that interacts with
|
| ... | ... | @@ -72,14 +101,35 @@ export const TorProviders = Object.freeze({ |
| 72 | 101 | */
|
| 73 | 102 | export class TorProviderBuilder {
|
| 74 | 103 | /**
|
| 75 | - * A promise with the instance of the provider that we are using.
|
|
| 104 | + * Data about the current provider instance.
|
|
| 76 | 105 | *
|
| 77 | - * @type {Promise<TorProvider>?}
|
|
| 106 | + * @type {?TorProviderData}
|
|
| 78 | 107 | */
|
| 79 | - static #provider = null;
|
|
| 108 | + static #providerData = null;
|
|
| 80 | 109 | |
| 81 | 110 | /**
|
| 82 | - * A record of the log messages from all TorProvider instances.
|
|
| 111 | + * Whether the `uninit` method has been called.
|
|
| 112 | + *
|
|
| 113 | + * @type {boolean}
|
|
| 114 | + */
|
|
| 115 | + static #uninitialized = false;
|
|
| 116 | + |
|
| 117 | + /**
|
|
| 118 | + * Check that we are active before a public call.
|
|
| 119 | + *
|
|
| 120 | + * @throws {Error} Throws if we are not active.
|
|
| 121 | + */
|
|
| 122 | + static #checkActive() {
|
|
| 123 | + if (this.#uninitialized) {
|
|
| 124 | + throw new Error("TorProviderBuilder has already been uninitialized.");
|
|
| 125 | + }
|
|
| 126 | + if (!this.#providerData) {
|
|
| 127 | + throw new Error("TorProviderBuilder has not been initialized.");
|
|
| 128 | + }
|
|
| 129 | + }
|
|
| 130 | + |
|
| 131 | + /**
|
|
| 132 | + * A record of the log messages from all provider instances.
|
|
| 83 | 133 | *
|
| 84 | 134 | * @type {LogEntry[]}
|
| 85 | 135 | */
|
| ... | ... | @@ -120,22 +170,6 @@ export class TorProviderBuilder { |
| 120 | 170 | this.#log.push(logEntry);
|
| 121 | 171 | }
|
| 122 | 172 | |
| 123 | - /**
|
|
| 124 | - * The observer that checks when the tor process exits, and reinitializes the
|
|
| 125 | - * provider.
|
|
| 126 | - *
|
|
| 127 | - * @type {Function}
|
|
| 128 | - */
|
|
| 129 | - static #exitObserver = null;
|
|
| 130 | - |
|
| 131 | - /**
|
|
| 132 | - * Tell whether the browser UI is ready.
|
|
| 133 | - * We ignore any errors until it is because we cannot show them.
|
|
| 134 | - *
|
|
| 135 | - * @type {boolean}
|
|
| 136 | - */
|
|
| 137 | - static #uiReady = false;
|
|
| 138 | - |
|
| 139 | 173 | /**
|
| 140 | 174 | * Initialize the provider of choice.
|
| 141 | 175 | */
|
| ... | ... | @@ -149,115 +183,214 @@ export class TorProviderBuilder { |
| 149 | 183 | };
|
| 150 | 184 | Services.obs.addObserver(this.#logObserver, TorProviderTopics.TorLog);
|
| 151 | 185 | |
| 186 | + // Even though initialization of the initial provider is asynchronous, we do
|
|
| 187 | + // not expect the caller to await it. The reason is that any call to build()
|
|
| 188 | + // will wait the initialization anyway (and re-throw any initialization
|
|
| 189 | + // error).
|
|
| 190 | + this.#replaceProvider();
|
|
| 191 | + }
|
|
| 192 | + |
|
| 193 | + /**
|
|
| 194 | + * Replace the provider with a new instance.
|
|
| 195 | + */
|
|
| 196 | + static #replaceProvider() {
|
|
| 197 | + // NOTE: We need to ensure that the #providerData is set as soon as
|
|
| 198 | + // TorProviderBuilder.init is called.
|
|
| 199 | + // I.e. it should be safe to call
|
|
| 200 | + // TorProviderBuilder.init();
|
|
| 201 | + // TorProviderBuilder.build();
|
|
| 202 | + // TorProviderBuilder.settledState();
|
|
| 203 | + // // etc
|
|
| 204 | + // without any await.
|
|
| 205 | + //
|
|
| 206 | + // In particular, this is needed by `TorConnect.init`, which will call
|
|
| 207 | + // `settledState`. It will also call `build` immediately if quickstart is
|
|
| 208 | + // set. See tor-browser#41921.
|
|
| 209 | + if (this.#providerData) {
|
|
| 210 | + lazy.logger.info(
|
|
| 211 | + `Replacing the provider with a "${this.providerType}" provider.`
|
|
| 212 | + );
|
|
| 213 | + } else {
|
|
| 214 | + lazy.logger.info(`Creating the initial "${this.providerType}" provider.`);
|
|
| 215 | + }
|
|
| 216 | + |
|
| 217 | + let providerClass;
|
|
| 152 | 218 | switch (this.providerType) {
|
| 153 | 219 | case TorProviders.tor:
|
| 154 | - // Even though initialization of the initial TorProvider is
|
|
| 155 | - // asynchronous, we do not expect the caller to await it. The reason is
|
|
| 156 | - // that any call to build() will wait the initialization anyway (and
|
|
| 157 | - // re-throw any initialization error).
|
|
| 158 | - this.#initTorProvider();
|
|
| 220 | + providerClass = lazy.TorProvider;
|
|
| 159 | 221 | break;
|
| 160 | 222 | case TorProviders.none:
|
| 161 | - lazy.TorLauncherUtil.setProxyConfiguration(
|
|
| 162 | - lazy.TorLauncherUtil.getPreferredSocksConfiguration()
|
|
| 163 | - );
|
|
| 223 | + providerClass = lazy.TorProviderNone;
|
|
| 164 | 224 | break;
|
| 165 | 225 | default:
|
| 166 | - console.error(`Unknown tor provider ${this.providerType}.`);
|
|
| 226 | + lazy.logger.error(`Unknown tor provider ${this.providerType}.`);
|
|
| 167 | 227 | break;
|
| 168 | 228 | }
|
| 229 | + // NOTE: It should be safe to create another provider instance whilst the
|
|
| 230 | + // existing one is still active. However, we will wait until the other is
|
|
| 231 | + // uninitialized before we initialize the new one.
|
|
| 232 | + const provider = new providerClass(() => {
|
|
| 233 | + this.#notifyStateChanged(provider);
|
|
| 234 | + });
|
|
| 235 | + const prevProviderData = this.#providerData;
|
|
| 236 | + // NOTE: We want `#providerData` to be set prior to our call to
|
|
| 237 | + // `provider.init`, so we create the `initPromise` prior to setting it.
|
|
| 238 | + const { promise: initPromise, resolve, reject } = Promise.withResolvers();
|
|
| 239 | + this.#providerData = { provider, initPromise };
|
|
| 240 | + // Let observers know we are restarting the provider.
|
|
| 241 | + this.#notifyStateChanged(provider);
|
|
| 242 | + |
|
| 243 | + // Run the rest of the init in an async operation that will cause
|
|
| 244 | + // `initPromise` to settle.
|
|
| 245 | + // NOTE: `#cleanupProviderData` should not throw, unlike `provider.init()`,
|
|
| 246 | + // which may throw.
|
|
| 247 | + // NOTE: We wait for `#cleanupProviderData` to complete before calling
|
|
| 248 | + // `provider.init()` in case the implementation relies on this.
|
|
| 249 | + this.#cleanupProviderData(prevProviderData).finally(() => {
|
|
| 250 | + provider.init().then(resolve, reject);
|
|
| 251 | + });
|
|
| 169 | 252 | }
|
| 170 | 253 | |
| 171 | 254 | /**
|
| 172 | - * Replace #provider with a new instance.
|
|
| 255 | + * Notify any listeners that the state of the current provider has changed.
|
|
| 173 | 256 | *
|
| 174 | - * @returns {Promise<TorProvider>} The new instance.
|
|
| 257 | + * @param {TorProviderBase} provider - The provider who's state has changed.
|
|
| 175 | 258 | */
|
| 176 | - static #initTorProvider() {
|
|
| 177 | - if (!this.#exitObserver) {
|
|
| 178 | - this.#exitObserver = this.#torExited.bind(this);
|
|
| 179 | - Services.obs.addObserver(
|
|
| 180 | - this.#exitObserver,
|
|
| 181 | - TorProviderTopics.ProcessExited
|
|
| 182 | - );
|
|
| 259 | + static #notifyStateChanged(provider) {
|
|
| 260 | + if (this.#uninitialized) {
|
|
| 261 | + // Do not signal the final state changes when we uninitialize.
|
|
| 262 | + return;
|
|
| 263 | + }
|
|
| 264 | + if (provider !== this.#providerData.provider) {
|
|
| 265 | + // Delayed call from an old provider. Ignore.
|
|
| 266 | + return;
|
|
| 183 | 267 | }
|
| 184 | 268 | |
| 185 | - // NOTE: We need to ensure that the #provider is set as soon
|
|
| 186 | - // TorProviderBuilder.init is called.
|
|
| 187 | - // I.e. it should be safe to call
|
|
| 188 | - // TorProviderBuilder.init();
|
|
| 189 | - // TorProviderBuilder.build();
|
|
| 190 | - // without any await.
|
|
| 191 | - //
|
|
| 192 | - // Therefore, we await the oldProvider within the Promise rather than make
|
|
| 193 | - // #initTorProvider async.
|
|
| 194 | - //
|
|
| 195 | - // In particular, this is needed by TorConnect when the user has selected
|
|
| 196 | - // quickstart, in which case `TorConnect.init` will immediately request the
|
|
| 197 | - // provider. See tor-browser#41921.
|
|
| 198 | - this.#provider = this.#replaceTorProvider(this.#provider);
|
|
| 199 | - return this.#provider;
|
|
| 269 | + Services.obs.notifyObservers(
|
|
| 270 | + null,
|
|
| 271 | + TorProviderTopics.ProviderStateChanged,
|
|
| 272 | + provider.state
|
|
| 273 | + );
|
|
| 274 | + |
|
| 275 | + this.#promptProviderState(false);
|
|
| 200 | 276 | }
|
| 201 | 277 | |
| 202 | 278 | /**
|
| 203 | - * Replace a TorProvider instance. Resolves once the TorProvider is
|
|
| 204 | - * initialised.
|
|
| 279 | + * Check the given provider's state.
|
|
| 280 | + *
|
|
| 281 | + * If the provider is no longer the current one, it is considered to be
|
|
| 282 | + * "Stopped".
|
|
| 283 | + *
|
|
| 284 | + * If it is still the current provider, the state will be updated and
|
|
| 285 | + * returned.
|
|
| 205 | 286 | *
|
| 206 | - * @param {Promise<TorProvider>?} oldProvider - The previous's provider's
|
|
| 207 | - * promise, if any.
|
|
| 208 | - * @returns {TorProvider} The new TorProvider instance.
|
|
| 287 | + * @param {TorProviderBase} provider - The provider to check.
|
|
| 288 | + * @returns {string} - The `TorProviderState` state for the provider.
|
|
| 209 | 289 | */
|
| 210 | - static async #replaceTorProvider(oldProvider) {
|
|
| 290 | + static #checkProviderState(provider) {
|
|
| 291 | + if (this.#providerData?.provider !== provider) {
|
|
| 292 | + // Replaced.
|
|
| 293 | + lazy.logger.debug("The checked provider has been replaced.");
|
|
| 294 | + return TorProviderState.Stopped;
|
|
| 295 | + }
|
|
| 296 | + return this.#providerData.provider.state;
|
|
| 297 | + }
|
|
| 298 | + |
|
| 299 | + /**
|
|
| 300 | + * Cleanup the given provider data.
|
|
| 301 | + *
|
|
| 302 | + * @param {?TorProviderData} providerData - The data to clean up.
|
|
| 303 | + */
|
|
| 304 | + static async #cleanupProviderData(providerData) {
|
|
| 305 | + if (!providerData) {
|
|
| 306 | + return;
|
|
| 307 | + }
|
|
| 211 | 308 | try {
|
| 212 | - // Uninitialise the old TorProvider, if there is any.
|
|
| 213 | - (await oldProvider)?.uninit();
|
|
| 309 | + await providerData.initPromise;
|
|
| 214 | 310 | } catch {}
|
| 215 | - const provider = new lazy.TorProvider();
|
|
| 311 | + |
|
| 312 | + // Call `uninit` to clean up, even if `init` threw.
|
|
| 313 | + // Should be safe to call more than once (via `uninit`).
|
|
| 216 | 314 | try {
|
| 217 | - await provider.init();
|
|
| 315 | + await providerData.provider.uninit();
|
|
| 218 | 316 | } catch (error) {
|
| 219 | - // Wrap in an error type for callers to know whether the error comes from
|
|
| 220 | - // initialisation or something else.
|
|
| 221 | - throw new TorProviderInitError(error);
|
|
| 317 | + lazy.logger.error("Error in uninitializing provider", error);
|
|
| 222 | 318 | }
|
| 223 | - return provider;
|
|
| 224 | 319 | }
|
| 225 | 320 | |
| 226 | 321 | static uninit() {
|
| 227 | - this.#provider?.then(provider => {
|
|
| 228 | - provider.uninit();
|
|
| 229 | - this.#provider = null;
|
|
| 230 | - });
|
|
| 231 | - if (this.#exitObserver) {
|
|
| 232 | - Services.obs.removeObserver(
|
|
| 233 | - this.#exitObserver,
|
|
| 234 | - TorProviderTopics.ProcessExited
|
|
| 235 | - );
|
|
| 236 | - this.#exitObserver = null;
|
|
| 237 | - }
|
|
| 322 | + this.#uninitialized = true;
|
|
| 323 | + |
|
| 324 | + // NOTE: `uninit` should not be followed by any further calls to public
|
|
| 325 | + // methods. So we can clear the `#providerData` without keeping it for any
|
|
| 326 | + // future provider instances to wait on.
|
|
| 327 | + const providerData = this.#providerData;
|
|
| 328 | + this.#providerData = null;
|
|
| 329 | + this.#cleanupProviderData(providerData);
|
|
| 330 | + |
|
| 238 | 331 | Services.obs.removeObserver(this.#logObserver, TorProviderTopics.TorLog);
|
| 239 | 332 | }
|
| 240 | 333 | |
| 241 | 334 | /**
|
| 242 | - * Build a provider.
|
|
| 243 | - * This method will wait for the system to be initialized, and allows you to
|
|
| 244 | - * catch also any initialization errors.
|
|
| 335 | + * Request the current instance of the Tor provider.
|
|
| 336 | + *
|
|
| 337 | + * This method will wait for the system to be initialized before returning the
|
|
| 338 | + * provider.
|
|
| 339 | + *
|
|
| 340 | + * This will throw any initialization errors of the provider, if it had any.
|
|
| 341 | + * This will also throw if the provider is no longer active.
|
|
| 245 | 342 | *
|
| 246 | 343 | * @returns {TorProvider} A TorProvider instance
|
| 247 | 344 | */
|
| 248 | 345 | static async build() {
|
| 249 | - if (!this.#provider && this.providerType === TorProviders.none) {
|
|
| 346 | + this.#checkActive();
|
|
| 347 | + if (this.#providerData.provider instanceof lazy.TorProviderNone) {
|
|
| 250 | 348 | throw new Error(
|
| 251 | 349 | "Tor Browser has been configured to use only the proxy functionalities."
|
| 252 | 350 | );
|
| 253 | - } else if (!this.#provider) {
|
|
| 254 | - throw new Error(
|
|
| 255 | - "The provider has not been initialized or already uninitialized."
|
|
| 351 | + }
|
|
| 352 | + |
|
| 353 | + const { provider, initPromise } = this.#providerData;
|
|
| 354 | + // initPromise may throw.
|
|
| 355 | + await initPromise;
|
|
| 356 | + if (this.#checkProviderState(provider) !== TorProviderState.Running) {
|
|
| 357 | + lazy.logger.warn("Request was made for a provider that has stopped.");
|
|
| 358 | + // TODO: Wait for the new instance instead?
|
|
| 359 | + throw new TorProviderInitError(
|
|
| 360 | + new Error("Provider is no longer active.")
|
|
| 256 | 361 | );
|
| 257 | 362 | }
|
| 258 | - return this.#provider;
|
|
| 363 | + return provider;
|
|
| 364 | + }
|
|
| 365 | + |
|
| 366 | + /**
|
|
| 367 | + * Get the state of the current provider instance. Waits until the provider
|
|
| 368 | + * has finished initialisation first.
|
|
| 369 | + *
|
|
| 370 | + * If the provider has been replaced, the Stopped state will be returned.
|
|
| 371 | + *
|
|
| 372 | + * @returns {string} - The `TorProviderState` state for the provider that
|
|
| 373 | + * existed when this method was called.
|
|
| 374 | + */
|
|
| 375 | + static async settledState() {
|
|
| 376 | + this.#checkActive();
|
|
| 377 | + const { provider, initPromise } = this.#providerData;
|
|
| 378 | + try {
|
|
| 379 | + await initPromise;
|
|
| 380 | + } catch {}
|
|
| 381 | + return this.#checkProviderState(provider);
|
|
| 259 | 382 | }
|
| 260 | 383 | |
| 384 | + /**
|
|
| 385 | + * Replace the current provider instance with a new provider.
|
|
| 386 | + */
|
|
| 387 | + static replace() {
|
|
| 388 | + this.#checkActive();
|
|
| 389 | + this.#replaceProvider();
|
|
| 390 | + }
|
|
| 391 | + |
|
| 392 | + // TODO: Remove firstWindowLoaded, #uiReady, #prompting, #promptProviderState
|
|
| 393 | + // and use TorConnect instead. tor-browser#43570.
|
|
| 261 | 394 | /**
|
| 262 | 395 | * Check if the provider has been succesfully initialized when the first
|
| 263 | 396 | * browser window is shown.
|
| ... | ... | @@ -266,50 +399,72 @@ export class TorProviderBuilder { |
| 266 | 399 | * but we should modify TorConnect and about:torconnect to handle this case
|
| 267 | 400 | * there with a better UX.
|
| 268 | 401 | */
|
| 269 | - static async firstWindowLoaded() {
|
|
| 270 | - // FIXME: Just integrate this with the about:torconnect or about:tor UI.
|
|
| 271 | - if (
|
|
| 272 | - !lazy.TorLauncherUtil.shouldStartAndOwnTor ||
|
|
| 273 | - this.providerType !== TorProviders.tor
|
|
| 274 | - ) {
|
|
| 275 | - // If we are not managing the Tor daemon we cannot restart it, so just
|
|
| 276 | - // early return.
|
|
| 277 | - return;
|
|
| 278 | - }
|
|
| 279 | - let running = false;
|
|
| 280 | - try {
|
|
| 281 | - const provider = await this.#provider;
|
|
| 282 | - // The initialization might have succeeded, but so far we have ignored any
|
|
| 283 | - // error notification. So, check that the process has not exited after the
|
|
| 284 | - // provider has been initialized successfully, but the UI was not ready
|
|
| 285 | - // yet.
|
|
| 286 | - running = provider.isRunning;
|
|
| 287 | - } catch {
|
|
| 288 | - // Not even initialized, running is already false.
|
|
| 289 | - }
|
|
| 290 | - while (!running && lazy.TorLauncherUtil.showRestartPrompt(true)) {
|
|
| 291 | - try {
|
|
| 292 | - await this.#initTorProvider();
|
|
| 293 | - running = true;
|
|
| 294 | - } catch {}
|
|
| 295 | - }
|
|
| 296 | - // The user might have canceled the restart, but at this point the UI is
|
|
| 297 | - // ready in any case.
|
|
| 298 | - this.#uiReady = true;
|
|
| 402 | + static firstWindowLoaded() {
|
|
| 403 | + this.#promptProviderState(true);
|
|
| 299 | 404 | }
|
| 300 | 405 | |
| 301 | - static async #torExited() {
|
|
| 406 | + /**
|
|
| 407 | + * Tell whether the browser UI is ready.
|
|
| 408 | + * We ignore any errors until it is because we cannot show them.
|
|
| 409 | + *
|
|
| 410 | + * @type {boolean}
|
|
| 411 | + */
|
|
| 412 | + static #uiReady = false;
|
|
| 413 | + |
|
| 414 | + /**
|
|
| 415 | + * Whether we are prompting the user for a restart of the provider.
|
|
| 416 | + *
|
|
| 417 | + * @type {boolean}
|
|
| 418 | + */
|
|
| 419 | + static #prompting = false;
|
|
| 420 | + |
|
| 421 | + /**
|
|
| 422 | + * Prompt the user to restart the provider, if this is necessary.
|
|
| 423 | + *
|
|
| 424 | + * @param {boolean} uiReady - Whether this is being called for the first time
|
|
| 425 | + * when the UI is ready.
|
|
| 426 | + */
|
|
| 427 | + static async #promptProviderState(uiReady) {
|
|
| 428 | + if (uiReady) {
|
|
| 429 | + this.#uiReady = true;
|
|
| 430 | + }
|
|
| 431 | + if (this.#providerData.provider.state === TorProviderState.Running) {
|
|
| 432 | + // Nothing to wait for.
|
|
| 433 | + return;
|
|
| 434 | + }
|
|
| 302 | 435 | if (!this.#uiReady) {
|
| 303 | - console.warn(
|
|
| 304 | - `Seen ${TorProviderTopics.ProcessExited}, but not doing anything because the UI is not ready yet.`
|
|
| 436 | + lazy.logger.warn(
|
|
| 437 | + "Seen exit, but not doing anything because the UI is not ready yet."
|
|
| 305 | 438 | );
|
| 306 | 439 | return;
|
| 307 | 440 | }
|
| 308 | - while (lazy.TorLauncherUtil.showRestartPrompt(false)) {
|
|
| 309 | - try {
|
|
| 310 | - await this.#initTorProvider();
|
|
| 311 | - break;
|
|
| 312 | - } catch {}
|
|
| 441 | + if (this.#prompting) {
|
|
| 442 | + // Already prompting, so don't duplicate.
|
|
| 443 | + return;
|
|
| 444 | + }
|
|
| 445 | + |
|
| 446 | + this.#prompting = true;
|
|
| 447 | + let waitForInit = uiReady;
|
|
| 448 | + let retry = true;
|
|
| 449 | + try {
|
|
| 450 | + while (retry) {
|
|
| 451 | + if (waitForInit) {
|
|
| 452 | + try {
|
|
| 453 | + await this.#providerData.initPromise;
|
|
| 454 | + } catch {}
|
|
| 455 | + }
|
|
| 456 | + if (
|
|
| 457 | + this.#providerData.provider.state === TorProviderState.Stopped &&
|
|
| 458 | + lazy.TorLauncherUtil.showRestartPrompt(uiReady)
|
|
| 459 | + ) {
|
|
| 460 | + waitForInit = true;
|
|
| 461 | + this.replace();
|
|
| 462 | + } else {
|
|
| 463 | + retry = false;
|
|
| 464 | + }
|
|
| 465 | + }
|
|
| 466 | + } finally {
|
|
| 467 | + this.#prompting = false;
|
|
| 313 | 468 | }
|
| 314 | 469 | }
|
| 315 | 470 | |
| ... | ... | @@ -320,7 +475,7 @@ export class TorProviderBuilder { |
| 320 | 475 | * Otherwise, if it is not valid, the C tor implementation is chosen as the
|
| 321 | 476 | * default one.
|
| 322 | 477 | *
|
| 323 | - * @returns {number} An entry from TorProviders
|
|
| 478 | + * @returns {string} An entry from TorProviders
|
|
| 324 | 479 | */
|
| 325 | 480 | static get providerType() {
|
| 326 | 481 | // TODO: Add a preference to permanently save this without and avoid always
|
| 1 | +import { TorProviderBase } from "resource://gre/modules/TorProviderBase.sys.mjs";
|
|
| 2 | + |
|
| 3 | +const lazy = {};
|
|
| 4 | +ChromeUtils.defineESModuleGetters(lazy, {
|
|
| 5 | + TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
|
|
| 6 | +});
|
|
| 7 | + |
|
| 8 | +/**
|
|
| 9 | + * A provider that only sets the proxy settings.
|
|
| 10 | + */
|
|
| 11 | +export class TorProviderNone extends TorProviderBase {
|
|
| 12 | + async _initInternal() {
|
|
| 13 | + lazy.TorLauncherUtil.setProxyConfiguration(
|
|
| 14 | + lazy.TorLauncherUtil.getPreferredSocksConfiguration()
|
|
| 15 | + );
|
|
| 16 | + }
|
|
| 17 | + |
|
| 18 | + async _uninitInternal() {}
|
|
| 19 | +} |
| ... | ... | @@ -7,7 +7,9 @@ EXTRA_JS_MODULES += [ |
| 7 | 7 | "TorProcess.sys.mjs",
|
| 8 | 8 | "TorProcessAndroid.sys.mjs",
|
| 9 | 9 | "TorProvider.sys.mjs",
|
| 10 | + "TorProviderBase.sys.mjs",
|
|
| 10 | 11 | "TorProviderBuilder.sys.mjs",
|
| 12 | + "TorProviderNone.sys.mjs",
|
|
| 11 | 13 | "TorStartupService.sys.mjs",
|
| 12 | 14 | ]
|
| 13 | 15 |
| ... | ... | @@ -9,6 +9,7 @@ const lazy = {}; |
| 9 | 9 | ChromeUtils.defineESModuleGetters(lazy, {
|
| 10 | 10 | MoatRPC: "resource://gre/modules/Moat.sys.mjs",
|
| 11 | 11 | TorBootstrapRequest: "resource://gre/modules/TorBootstrapRequest.sys.mjs",
|
| 12 | + TorProviderState: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
| 12 | 13 | TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
| 13 | 14 | TorBootstrapError: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
| 14 | 15 | TorProviderInitError: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
| ... | ... | @@ -913,7 +914,7 @@ export const TorConnect = { |
| 913 | 914 | };
|
| 914 | 915 | |
| 915 | 916 | // register the Tor topics we always care about
|
| 916 | - observeTopic(lazy.TorProviderTopics.ProcessExited);
|
|
| 917 | + observeTopic(lazy.TorProviderTopics.ProviderStateChanged);
|
|
| 917 | 918 | observeTopic(lazy.TorProviderTopics.HasWarnOrErr);
|
| 918 | 919 | observeTopic(lazy.TorSettingsTopics.SettingsChanged);
|
| 919 | 920 | observeTopic(NETWORK_LINK_TOPIC);
|
| ... | ... | @@ -933,7 +934,7 @@ export const TorConnect = { |
| 933 | 934 | }
|
| 934 | 935 | },
|
| 935 | 936 | |
| 936 | - async observe(subject, topic) {
|
|
| 937 | + async observe(subject, topic, data) {
|
|
| 937 | 938 | lazy.logger.debug(`Observed ${topic}`);
|
| 938 | 939 | |
| 939 | 940 | switch (topic) {
|
| ... | ... | @@ -947,7 +948,10 @@ export const TorConnect = { |
| 947 | 948 | this._notifyBootstrapProgress();
|
| 948 | 949 | }
|
| 949 | 950 | break;
|
| 950 | - case lazy.TorProviderTopics.ProcessExited:
|
|
| 951 | + case lazy.TorProviderTopics.ProviderStateChanged:
|
|
| 952 | + if (data !== lazy.TorProviderState.Stopped) {
|
|
| 953 | + break;
|
|
| 954 | + }
|
|
| 951 | 955 | lazy.logger.info("Starting again since the tor process exited");
|
| 952 | 956 | // Treat a failure as a possibly broken configuration.
|
| 953 | 957 | // So, prevent quickstart at the next start.
|
| ... | ... | @@ -1341,7 +1345,7 @@ export const TorConnect = { |
| 1341 | 1345 | this._tryAgain = true;
|
| 1342 | 1346 | |
| 1343 | 1347 | if (error instanceof lazy.TorProviderInitError) {
|
| 1344 | - // Treat like TorProviderTopics.ProcessExited. We expect a user
|
|
| 1348 | + // Treat like TorProviderTopics.ProviderStateChanged. We expect a user
|
|
| 1345 | 1349 | // notification when this happens.
|
| 1346 | 1350 | // Treat a failure as a possibly broken configuration.
|
| 1347 | 1351 | // So, prevent quickstart at the next start.
|
| ... | ... | @@ -9,6 +9,7 @@ ChromeUtils.defineESModuleGetters(lazy, { |
| 9 | 9 | Lox: "resource://gre/modules/Lox.sys.mjs",
|
| 10 | 10 | LoxTopics: "resource://gre/modules/Lox.sys.mjs",
|
| 11 | 11 | TorParsers: "resource://gre/modules/TorParsers.sys.mjs",
|
| 12 | + TorProviderState: "resource://gre/modules/TorProviderBuilder.sys.mjs",
|
|
| 12 | 13 | });
|
| 13 | 14 | |
| 14 | 15 | ChromeUtils.defineLazyGetter(lazy, "logger", () => {
|
| ... | ... | @@ -940,7 +941,10 @@ class TorSettingsImpl { |
| 940 | 941 | |
| 941 | 942 | // Test whether the provider is no longer running or has been replaced.
|
| 942 | 943 | const providerRunning = () => {
|
| 943 | - return providerRef === this.#providerRef && provider.isRunning;
|
|
| 944 | + return (
|
|
| 945 | + providerRef === this.#providerRef &&
|
|
| 946 | + provider.state !== lazy.TorProviderState.Stopped
|
|
| 947 | + );
|
|
| 944 | 948 | };
|
| 945 | 949 | |
| 946 | 950 | lazy.logger.debug("Passing on settings to the provider", apply, details);
|