[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [Git][tpo/applications/tor-browser][tor-browser-115.9.0esr-13.5-1] 20 commits: fixup! Bug 40597: Implement TorSettings module



Title: GitLab

Pier Angelo Vendrame pushed to branch tor-browser-115.9.0esr-13.5-1 at The Tor Project / Applications / Tor Browser

Commits:

  • b8fbdc2b
    by Pier Angelo Vendrame at 2024-03-19T08:19:47+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Use actual private members in InternetTest, and unify the test and
    testAsync methods.
    
  • 2b75d115
    by Pier Angelo Vendrame at 2024-03-19T08:19:54+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    The purpose of this commit is just to make the review easier.
    It moves the various state callbacks in another part of the file (and
    gives them names, to refer to them).
    They are not linted/formatted on purpose, to allow checking that the
    changes are minimal with the `--color-moved` option.
    
  • 0c5e98ab
    by Pier Angelo Vendrame at 2024-03-19T08:19:54+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    The purpose of this commit is just to make the review easier.
    It lints the previous commit to make it closer to the next form it will
    take.
    To review this commit, you can lint TorConnect.sys.mjs and check you
    obtain the same result.
    
  • 8708b47a
    by Pier Angelo Vendrame at 2024-03-19T08:19:54+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Add a specialized class for every state.
    Removed TorConnectStateTransitions and store the valid transitions
    inside the new classes.
    
  • d5d15e38
    by Pier Angelo Vendrame at 2024-03-19T08:19:55+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Removes the context from StateCallback.
    Instead, we will always initialize new objects.
    
  • 929972af
    by Pier Angelo Vendrame at 2024-03-19T08:19:55+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Fix no-async-promise-executor on TorConnect.
    
    Move the responsibility of starting the next state callback and broadcat
    the state change from the begin function to the transition function.
    This is what will actually empower us to remove the async promise
    executor, because they will not have to block the state change anymore.
    
  • f9d3e162
    by Pier Angelo Vendrame at 2024-03-19T08:19:55+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Fix no-async-promise-executor on TorConnect.
    
    Now that callbacks have been lifted from the responsibility of blocking
    the transition, they do not have to explicitly resolve anymore.
    So, get rid of the various promises, and make the old callbacks regular
    methods of the corresponding classes.
    
    Also, updated the documentation.
    
  • d214f39b
    by Pier Angelo Vendrame at 2024-03-19T08:19:56+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Add the changeState method to StateCallback, other minor changes.
    
  • 0e97bf55
    by Pier Angelo Vendrame at 2024-03-19T08:19:56+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Move some parts of BootstrappingState.run to methods on their own, to
    make it easier to understand this function.
    
  • 1243f925
    by Pier Angelo Vendrame at 2024-03-19T08:19:56+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Various refactors to AutoBootstrappingState.
    
  • 34868388
    by Pier Angelo Vendrame at 2024-03-19T08:19:57+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    TorConnect does not need to be defined in a function.
    
  • 7218d77c
    by Pier Angelo Vendrame at 2024-03-19T08:19:57+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    First batch of changes requested in the review.
    
  • 546449ce
    by Pier Angelo Vendrame at 2024-03-19T08:19:57+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    More changes on the InternetTest class.
    
  • 5fe62195
    by Pier Angelo Vendrame at 2024-03-19T08:19:58+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Additional refactor to the AutoBootstrappingState: split the run in
    more methods, removed some layers of try-catch.
    
  • becb5e4b
    by Pier Angelo Vendrame at 2024-03-19T08:19:58+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Additional refactor to the AutoBootstrappingState: be consistent in
    initializing private members and document that in case of race some
    variables might be set to undefined.
    Also, do not store the bootstrap as a member, to avoid possible race
    conditions.
    
  • 1343ed70
    by Pier Angelo Vendrame at 2024-03-19T08:19:58+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    When simulating censorship, do not trigger an additional change of state
    if one is already happening.
    
  • 6bdd9dcd
    by Pier Angelo Vendrame at 2024-03-19T08:19:59+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Check if the current state is already transitioning, and refusing any
    additional transition request.
    
  • 22b986b8
    by Pier Angelo Vendrame at 2024-03-19T08:19:59+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Do not await TorSettings.applySettings after a successfull bootstrap.
    Instead, we use .catch and log any unexpected results.
    
  • f59ddd26
    by Pier Angelo Vendrame at 2024-03-19T08:19:59+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Ignore cancel requests when in non-bootstrapping states.
    
  • 3788b1ae
    by Pier Angelo Vendrame at 2024-03-19T08:20:00+01:00
    fixup! Bug 40597: Implement TorSettings module
    
    Bug 41114: Refactor TorConnect.
    
    Pass only the new state's name to StateCallback.end.
    

2 changed files:

Changes:

  • toolkit/modules/Moat.sys.mjs
    ... ... @@ -85,9 +85,13 @@ export class MoatRPC {
    85 85
           TorLauncherPrefs.bridgedb_reflector
    
    86 86
         );
    
    87 87
         const front = Services.prefs.getStringPref(TorLauncherPrefs.bridgedb_front);
    
    88
    -    const builder = new lazy.DomainFrontRequestBuilder();
    
    89
    -    await builder.init(reflector, front);
    
    90
    -    this.#requestBuilder = builder;
    
    88
    +    this.#requestBuilder = new lazy.DomainFrontRequestBuilder();
    
    89
    +    try {
    
    90
    +      await this.#requestBuilder.init(reflector, front);
    
    91
    +    } catch (e) {
    
    92
    +      this.#requestBuilder = null;
    
    93
    +      throw e;
    
    94
    +    }
    
    91 95
       }
    
    92 96
     
    
    93 97
       async uninit() {
    

  • toolkit/modules/TorConnect.sys.mjs
    ... ... @@ -117,52 +117,6 @@ XPCOMUtils.defineLazyGetter(
    117 117
                 └───────────────────────┘
    
    118 118
     */
    
    119 119
     
    
    120
    -/* Maps allowed state transitions
    
    121
    -   TorConnectStateTransitions[state] maps to an array of allowed states to transition to
    
    122
    -   This is just an encoding of the above transition diagram that we verify at runtime
    
    123
    -*/
    
    124
    -const TorConnectStateTransitions = Object.freeze(
    
    125
    -  new Map([
    
    126
    -    [
    
    127
    -      TorConnectState.Initial,
    
    128
    -      [
    
    129
    -        TorConnectState.Disabled,
    
    130
    -        TorConnectState.Bootstrapping,
    
    131
    -        TorConnectState.Configuring,
    
    132
    -        TorConnectState.Error,
    
    133
    -      ],
    
    134
    -    ],
    
    135
    -    [
    
    136
    -      TorConnectState.Configuring,
    
    137
    -      [
    
    138
    -        TorConnectState.AutoBootstrapping,
    
    139
    -        TorConnectState.Bootstrapping,
    
    140
    -        TorConnectState.Error,
    
    141
    -      ],
    
    142
    -    ],
    
    143
    -    [
    
    144
    -      TorConnectState.AutoBootstrapping,
    
    145
    -      [
    
    146
    -        TorConnectState.Configuring,
    
    147
    -        TorConnectState.Bootstrapped,
    
    148
    -        TorConnectState.Error,
    
    149
    -      ],
    
    150
    -    ],
    
    151
    -    [
    
    152
    -      TorConnectState.Bootstrapping,
    
    153
    -      [
    
    154
    -        TorConnectState.Configuring,
    
    155
    -        TorConnectState.Bootstrapped,
    
    156
    -        TorConnectState.Error,
    
    157
    -      ],
    
    158
    -    ],
    
    159
    -    [TorConnectState.Error, [TorConnectState.Configuring]],
    
    160
    -    [TorConnectState.Bootstrapped, [TorConnectState.Configuring]],
    
    161
    -    // terminal states
    
    162
    -    [TorConnectState.Disabled, []],
    
    163
    -  ])
    
    164
    -);
    
    165
    -
    
    166 120
     /* Topics Notified by the TorConnect module */
    
    167 121
     export const TorConnectTopics = Object.freeze({
    
    168 122
       StateChange: "torconnect:state-change",
    
    ... ... @@ -171,81 +125,612 @@ export const TorConnectTopics = Object.freeze({
    171 125
       BootstrapError: "torconnect:bootstrap-error",
    
    172 126
     });
    
    173 127
     
    
    174
    -// The StateCallback is a wrapper around an async function which executes during
    
    175
    -// the lifetime of a TorConnect State. A system is also provided to allow this
    
    176
    -// ongoing function to early-out via a per StateCallback on_transition callback
    
    177
    -// which may be called externally when we need to early-out and move on to another
    
    178
    -// state (for example, from Bootstrapping to Configuring in the event the user
    
    179
    -// cancels a bootstrap attempt)
    
    128
    +// The StateCallback is the base class to implement the various states.
    
    129
    +// All states should extend it and implement a `run` function, which can
    
    130
    +// optionally be async, and define an array of valid transitions.
    
    131
    +// The parent class will handle everything else, including the transition to
    
    132
    +// other states when the run function is complete etc...
    
    133
    +// A system is also provided to allow this function to early-out. The runner
    
    134
    +// should check the transitioning getter when appropriate and return.
    
    135
    +// In addition to that, a state can implement a transitionRequested callback,
    
    136
    +// which can be used in conjunction with a mechanism like Promise.race.
    
    137
    +// This allows to handle, for example, users' requests to cancel a bootstrap
    
    138
    +// attempt.
    
    139
    +// A state can optionally define a cleanup function, that will be run in all
    
    140
    +// cases before transitioning to the next state.
    
    180 141
     class StateCallback {
    
    181
    -  constructor(state, callback) {
    
    182
    -    this._state = state;
    
    183
    -    this._callback = callback;
    
    184
    -    this._init();
    
    185
    -  }
    
    142
    +  #state;
    
    143
    +  #promise;
    
    144
    +  #transitioning = false;
    
    186 145
     
    
    187
    -  _init() {
    
    188
    -    // this context object is bound to the callback each time transition is
    
    189
    -    // attempted via begin()
    
    190
    -    this._context = {
    
    191
    -      // This callback may be overwritten in the _callback for each state
    
    192
    -      // States may have various pieces of work which need to occur
    
    193
    -      // before they can be exited (eg resource cleanup)
    
    194
    -      // See the _stateCallbacks map for examples
    
    195
    -      on_transition: nextState => {},
    
    196
    -
    
    197
    -      // flag used to determine if a StateCallback should early-out
    
    198
    -      // its work
    
    199
    -      _transitioning: false,
    
    200
    -
    
    201
    -      // may be called within the StateCallback to determine if exit is possible
    
    202
    -      get transitioning() {
    
    203
    -        return this._transitioning;
    
    204
    -      },
    
    205
    -    };
    
    146
    +  constructor(stateName) {
    
    147
    +    this.#state = stateName;
    
    206 148
       }
    
    207 149
     
    
    208 150
       async begin(...args) {
    
    209
    -    lazy.logger.trace(`Entering ${this._state} state`);
    
    210
    -    this._init();
    
    151
    +    lazy.logger.trace(`Entering ${this.#state} state`);
    
    152
    +    // Make sure we always have an actual promise.
    
    211 153
         try {
    
    212
    -      // this Promise will block until this StateCallback has completed its work
    
    213
    -      await Promise.resolve(this._callback.call(this._context, ...args));
    
    214
    -      lazy.logger.info(`Exited ${this._state} state`);
    
    215
    -
    
    216
    -      // handled state transition
    
    217
    -      Services.obs.notifyObservers(
    
    218
    -        { state: this._nextState },
    
    219
    -        TorConnectTopics.StateChange
    
    220
    -      );
    
    221
    -      TorConnect._callback(this._nextState).begin(...this._nextStateArgs);
    
    222
    -    } catch (obj) {
    
    223
    -      TorConnect._changeState(
    
    224
    -        TorConnectState.Error,
    
    225
    -        obj?.message,
    
    226
    -        obj?.details
    
    154
    +      this.#promise = Promise.resolve(this.run(...args));
    
    155
    +    } catch (err) {
    
    156
    +      this.#promise = Promise.reject(err);
    
    157
    +    }
    
    158
    +    try {
    
    159
    +      // If the callback throws, transition to error as soon as possible.
    
    160
    +      await this.#promise;
    
    161
    +      lazy.logger.info(`${this.#state}'s run is done`);
    
    162
    +    } catch (err) {
    
    163
    +      if (this.transitioning) {
    
    164
    +        lazy.logger.error(
    
    165
    +          `A transition from ${
    
    166
    +            this.#state
    
    167
    +          } is already happening, silencing this exception.`,
    
    168
    +          err
    
    169
    +        );
    
    170
    +        return;
    
    171
    +      }
    
    172
    +      lazy.logger.error(
    
    173
    +        `${this.#state}'s run threw, transitioning to the Error state.`,
    
    174
    +        err
    
    227 175
           );
    
    176
    +      this.changeState(TorConnectState.Error, err?.message, err?.details);
    
    177
    +    }
    
    178
    +  }
    
    179
    +
    
    180
    +  async end(nextState) {
    
    181
    +    lazy.logger.trace(
    
    182
    +      `Ending state ${this.#state} (to transition to ${nextState})`
    
    183
    +    );
    
    184
    +
    
    185
    +    if (this.#transitioning) {
    
    186
    +      // Should we check turn this into an error?
    
    187
    +      // It will make dealing with the error state harder.
    
    188
    +      lazy.logger.warn("this.#transitioning is already true.");
    
    189
    +    }
    
    190
    +
    
    191
    +    // Signal we should bail out ASAP.
    
    192
    +    this.#transitioning = true;
    
    193
    +    if (this.transitionRequested) {
    
    194
    +      this.transitionRequested();
    
    228 195
         }
    
    196
    +
    
    197
    +    lazy.logger.debug(
    
    198
    +      `Waiting for the ${
    
    199
    +        this.#state
    
    200
    +      }'s callback to return before the transition.`
    
    201
    +    );
    
    202
    +    try {
    
    203
    +      await this.#promise;
    
    204
    +    } finally {
    
    205
    +      lazy.logger.debug(`Calling ${this.#state}'s cleanup, if implemented.`);
    
    206
    +      if (this.cleanup) {
    
    207
    +        try {
    
    208
    +          await this.cleanup(nextState);
    
    209
    +          lazy.logger.debug(`${this.#state}'s cleanup function done.`);
    
    210
    +        } catch (e) {
    
    211
    +          lazy.logger.warn(`${this.#state}'s cleanup function threw.`, e);
    
    212
    +        }
    
    213
    +      }
    
    214
    +    }
    
    215
    +  }
    
    216
    +
    
    217
    +  changeState(stateName, ...args) {
    
    218
    +    TorConnect._changeState(stateName, ...args);
    
    229 219
       }
    
    230 220
     
    
    231
    -  transition(nextState, ...args) {
    
    232
    -    this._nextState = nextState;
    
    233
    -    this._nextStateArgs = [...args];
    
    221
    +  get transitioning() {
    
    222
    +    return this.#transitioning;
    
    223
    +  }
    
    234 224
     
    
    235
    -    // calls the on_transition callback to resolve any async work or do per-state cleanup
    
    236
    -    // this call to on_transition should resolve the async work currentlying going on in this.begin()
    
    237
    -    this._context.on_transition(nextState);
    
    238
    -    this._context._transitioning = true;
    
    225
    +  get state() {
    
    226
    +    return this.#state;
    
    239 227
       }
    
    240 228
     }
    
    241 229
     
    
    242 230
     // async method to sleep for a given amount of time
    
    243
    -const debug_sleep = async ms => {
    
    231
    +const debugSleep = async ms => {
    
    244 232
       return new Promise((resolve, reject) => {
    
    245 233
         setTimeout(resolve, ms);
    
    246 234
       });
    
    247 235
     };
    
    248 236
     
    
    237
    +class InitialState extends StateCallback {
    
    238
    +  allowedTransitions = Object.freeze([
    
    239
    +    TorConnectState.Disabled,
    
    240
    +    TorConnectState.Bootstrapping,
    
    241
    +    TorConnectState.Configuring,
    
    242
    +    TorConnectState.Error,
    
    243
    +  ]);
    
    244
    +
    
    245
    +  constructor() {
    
    246
    +    super(TorConnectState.Initial);
    
    247
    +  }
    
    248
    +
    
    249
    +  run() {
    
    250
    +    // TODO: Block this transition until we successfully build a TorProvider.
    
    251
    +  }
    
    252
    +}
    
    253
    +
    
    254
    +class ConfiguringState extends StateCallback {
    
    255
    +  allowedTransitions = Object.freeze([
    
    256
    +    TorConnectState.AutoBootstrapping,
    
    257
    +    TorConnectState.Bootstrapping,
    
    258
    +    TorConnectState.Error,
    
    259
    +  ]);
    
    260
    +
    
    261
    +  constructor() {
    
    262
    +    super(TorConnectState.Configuring);
    
    263
    +  }
    
    264
    +
    
    265
    +  run() {
    
    266
    +    // The configuring state does not do anything.
    
    267
    +  }
    
    268
    +}
    
    269
    +
    
    270
    +class BootstrappingState extends StateCallback {
    
    271
    +  #bootstrap = null;
    
    272
    +  #bootstrapError = "";
    
    273
    +  #bootstrapErrorDetails = "";
    
    274
    +  #internetTest = null;
    
    275
    +  #cancelled = false;
    
    276
    +
    
    277
    +  allowedTransitions = Object.freeze([
    
    278
    +    TorConnectState.Configuring,
    
    279
    +    TorConnectState.Bootstrapped,
    
    280
    +    TorConnectState.Error,
    
    281
    +  ]);
    
    282
    +
    
    283
    +  constructor() {
    
    284
    +    super(TorConnectState.Bootstrapping);
    
    285
    +  }
    
    286
    +
    
    287
    +  async run() {
    
    288
    +    if (await this.#simulateCensorship()) {
    
    289
    +      return;
    
    290
    +    }
    
    291
    +
    
    292
    +    this.#bootstrap = new lazy.TorBootstrapRequest();
    
    293
    +    this.#bootstrap.onbootstrapstatus = (progress, status) => {
    
    294
    +      TorConnect._updateBootstrapStatus(progress, status);
    
    295
    +    };
    
    296
    +    this.#bootstrap.onbootstrapcomplete = () => {
    
    297
    +      this.#internetTest.cancel();
    
    298
    +      this.changeState(TorConnectState.Bootstrapped);
    
    299
    +    };
    
    300
    +    this.#bootstrap.onbootstraperror = (message, details) => {
    
    301
    +      if (this.#cancelled) {
    
    302
    +        // We ignore this error since it occurred after cancelling (by the
    
    303
    +        // user). We assume the error is just a side effect of the cancelling.
    
    304
    +        // E.g. If the cancelling is triggered late in the process, we get
    
    305
    +        // "Building circuits: Establishing a Tor circuit failed".
    
    306
    +        // TODO: Maybe move this logic deeper in the process to know when to
    
    307
    +        // filter out such errors triggered by cancelling.
    
    308
    +        lazy.logger.warn(`Post-cancel error => ${message}; ${details}`);
    
    309
    +        return;
    
    310
    +      }
    
    311
    +      // We have to wait for the Internet test to finish before sending the
    
    312
    +      // bootstrap error
    
    313
    +      this.#bootstrapError = message;
    
    314
    +      this.#bootstrapErrorDetails = details;
    
    315
    +      this.#maybeTransitionToError();
    
    316
    +    };
    
    317
    +
    
    318
    +    this.#internetTest = new InternetTest();
    
    319
    +    this.#internetTest.onResult = status => {
    
    320
    +      TorConnect._internetStatus = status;
    
    321
    +      this.#maybeTransitionToError();
    
    322
    +    };
    
    323
    +    this.#internetTest.onError = () => {
    
    324
    +      this.#maybeTransitionToError();
    
    325
    +    };
    
    326
    +
    
    327
    +    this.#bootstrap.bootstrap();
    
    328
    +  }
    
    329
    +
    
    330
    +  async cleanup(nextState) {
    
    331
    +    if (nextState === TorConnectState.Configuring) {
    
    332
    +      // stop bootstrap process if user cancelled
    
    333
    +      this.#cancelled = true;
    
    334
    +      this.#internetTest?.cancel();
    
    335
    +      await this.#bootstrap?.cancel();
    
    336
    +    }
    
    337
    +  }
    
    338
    +
    
    339
    +  #maybeTransitionToError() {
    
    340
    +    if (
    
    341
    +      this.#internetTest.status === InternetStatus.Unknown &&
    
    342
    +      this.#internetTest.error === null &&
    
    343
    +      this.#internetTest.enabled
    
    344
    +    ) {
    
    345
    +      // We have been called by a failed bootstrap, but the internet test has
    
    346
    +      // not run yet - force it to run immediately!
    
    347
    +      this.#internetTest.test();
    
    348
    +      // Return from this call, because the Internet test's callback will call
    
    349
    +      // us again.
    
    350
    +      return;
    
    351
    +    }
    
    352
    +    // Do not transition to the offline error until we are sure that also the
    
    353
    +    // bootstrap failed, in case Moat is down but the bootstrap can proceed
    
    354
    +    // anyway.
    
    355
    +    if (this.#bootstrapError === "") {
    
    356
    +      return;
    
    357
    +    }
    
    358
    +    if (this.#internetTest.status === InternetStatus.Offline) {
    
    359
    +      this.changeState(
    
    360
    +        TorConnectState.Error,
    
    361
    +        TorStrings.torConnect.offline,
    
    362
    +        ""
    
    363
    +      );
    
    364
    +    } else {
    
    365
    +      // Give priority to the bootstrap error, in case the Internet test fails
    
    366
    +      TorConnect._hasBootstrapEverFailed = true;
    
    367
    +      this.changeState(
    
    368
    +        TorConnectState.Error,
    
    369
    +        this.#bootstrapError,
    
    370
    +        this.#bootstrapErrorDetails
    
    371
    +      );
    
    372
    +    }
    
    373
    +  }
    
    374
    +
    
    375
    +  async #simulateCensorship() {
    
    376
    +    // debug hook to simulate censorship preventing bootstrapping
    
    377
    +    const censorshipLevel = Services.prefs.getIntPref(
    
    378
    +      TorConnectPrefs.censorship_level,
    
    379
    +      0
    
    380
    +    );
    
    381
    +    if (censorshipLevel <= 0) {
    
    382
    +      return false;
    
    383
    +    }
    
    384
    +
    
    385
    +    await debugSleep(1500);
    
    386
    +    if (this.transitioning) {
    
    387
    +      // Already left this state.
    
    388
    +      return true;
    
    389
    +    }
    
    390
    +    TorConnect._hasBootstrapEverFailed = true;
    
    391
    +    if (censorshipLevel === 2) {
    
    392
    +      const codes = Object.keys(TorConnect._countryNames);
    
    393
    +      TorConnect._detectedLocation =
    
    394
    +        codes[Math.floor(Math.random() * codes.length)];
    
    395
    +    }
    
    396
    +    this.changeState(
    
    397
    +      TorConnectState.Error,
    
    398
    +      "Bootstrap failed (for debugging purposes)",
    
    399
    +      "Error: Censorship simulation"
    
    400
    +    );
    
    401
    +    return true;
    
    402
    +  }
    
    403
    +}
    
    404
    +
    
    405
    +class AutoBootstrappingState extends StateCallback {
    
    406
    +  #moat;
    
    407
    +  #settings;
    
    408
    +  #changedSettings = false;
    
    409
    +  #transitionPromise;
    
    410
    +  #transitionResolve;
    
    411
    +
    
    412
    +  allowedTransitions = Object.freeze([
    
    413
    +    TorConnectState.Configuring,
    
    414
    +    TorConnectState.Bootstrapped,
    
    415
    +    TorConnectState.Error,
    
    416
    +  ]);
    
    417
    +
    
    418
    +  constructor() {
    
    419
    +    super(TorConnectState.AutoBootstrapping);
    
    420
    +    this.#transitionPromise = new Promise(resolve => {
    
    421
    +      this.#transitionResolve = resolve;
    
    422
    +    });
    
    423
    +  }
    
    424
    +
    
    425
    +  async run(countryCode) {
    
    426
    +    if (await this.#simulateCensorship(countryCode)) {
    
    427
    +      return;
    
    428
    +    }
    
    429
    +    await this.#initMoat();
    
    430
    +    if (this.transitioning) {
    
    431
    +      return;
    
    432
    +    }
    
    433
    +    await this.#fetchSettings(countryCode);
    
    434
    +    if (this.transitioning) {
    
    435
    +      return;
    
    436
    +    }
    
    437
    +    await this.#trySettings();
    
    438
    +  }
    
    439
    +
    
    440
    +  /**
    
    441
    +   * Simulate a censorship event, if needed.
    
    442
    +   *
    
    443
    +   * @param {string} countryCode The country code passed to the state
    
    444
    +   * @returns {Promise<boolean>} true if we are simulating the censorship and
    
    445
    +   * the bootstrap should stop immediately, or false if the bootstrap should
    
    446
    +   * continue normally.
    
    447
    +   */
    
    448
    +  async #simulateCensorship(countryCode) {
    
    449
    +    const censorshipLevel = Services.prefs.getIntPref(
    
    450
    +      TorConnectPrefs.censorship_level,
    
    451
    +      0
    
    452
    +    );
    
    453
    +    if (censorshipLevel <= 0) {
    
    454
    +      return false;
    
    455
    +    }
    
    456
    +
    
    457
    +    // Very severe censorship: always fail even after manually selecting
    
    458
    +    // location specific settings.
    
    459
    +    if (censorshipLevel === 3) {
    
    460
    +      await debugSleep(2500);
    
    461
    +      if (!this.transitioning) {
    
    462
    +        this.changeState(
    
    463
    +          TorConnectState.Error,
    
    464
    +          "Error: censorship simulation",
    
    465
    +          ""
    
    466
    +        );
    
    467
    +      }
    
    468
    +      return true;
    
    469
    +    }
    
    470
    +
    
    471
    +    // Severe censorship: only fail after auto selecting, but succeed after
    
    472
    +    // manually selecting a country.
    
    473
    +    if (censorshipLevel === 2 && !countryCode) {
    
    474
    +      await debugSleep(2500);
    
    475
    +      if (!this.transitioning) {
    
    476
    +        this.changeState(
    
    477
    +          TorConnectState.Error,
    
    478
    +          "Error: Severe Censorship simulation",
    
    479
    +          ""
    
    480
    +        );
    
    481
    +      }
    
    482
    +      return true;
    
    483
    +    }
    
    484
    +
    
    485
    +    return false;
    
    486
    +  }
    
    487
    +
    
    488
    +  /**
    
    489
    +   * Initialize the MoatRPC to communicate with the backend.
    
    490
    +   */
    
    491
    +  async #initMoat() {
    
    492
    +    this.#moat = new lazy.MoatRPC();
    
    493
    +    // We need to wait Moat's initialization even when we are requested to
    
    494
    +    // transition to another state to be sure its uninit will have its intended
    
    495
    +    // effect. So, do not use Promise.race here.
    
    496
    +    await this.#moat.init();
    
    497
    +  }
    
    498
    +
    
    499
    +  /**
    
    500
    +   * Lookup user's potential censorship circumvention settings from Moat
    
    501
    +   * service.
    
    502
    +   */
    
    503
    +  async #fetchSettings(countryCode) {
    
    504
    +    // For now, throw any errors we receive from the backend, except when it was
    
    505
    +    // unable to detect user's country/region.
    
    506
    +    // If we use specialized error objects, we could pass the original errors to
    
    507
    +    // them.
    
    508
    +    const maybeSettings = await Promise.race([
    
    509
    +      this.#moat.circumvention_settings(
    
    510
    +        [...TorSettings.builtinBridgeTypes, "vanilla"],
    
    511
    +        countryCode
    
    512
    +      ),
    
    513
    +      // This might set maybeSettings to undefined.
    
    514
    +      this.#transitionPromise,
    
    515
    +    ]);
    
    516
    +    if (maybeSettings?.country) {
    
    517
    +      TorConnect._detectedLocation = maybeSettings.country;
    
    518
    +    }
    
    519
    +
    
    520
    +    if (maybeSettings?.settings && maybeSettings.settings.length) {
    
    521
    +      this.#settings = maybeSettings.settings;
    
    522
    +    } else if (!this.transitioning) {
    
    523
    +      // Keep consistency with the other call.
    
    524
    +      this.#settings = await Promise.race([
    
    525
    +        this.#moat.circumvention_defaults([
    
    526
    +          ...TorSettings.builtinBridgeTypes,
    
    527
    +          "vanilla",
    
    528
    +        ]),
    
    529
    +        // This might set this.#settings to undefined.
    
    530
    +        this.#transitionPromise,
    
    531
    +      ]);
    
    532
    +    }
    
    533
    +
    
    534
    +    if (!this.#settings?.length && !this.transitioning) {
    
    535
    +      // Both localized and fallback have, we can just throw to transition to
    
    536
    +      // the error state (but only if we aren't already transitioning).
    
    537
    +      // TODO: Let the UI layer localize the strings.
    
    538
    +
    
    539
    +      if (!TorConnect._detectedLocation) {
    
    540
    +        // unable to determine country
    
    541
    +        this.#throwError(
    
    542
    +          TorStrings.torConnect.autoBootstrappingFailed,
    
    543
    +          TorStrings.torConnect.cannotDetermineCountry
    
    544
    +        );
    
    545
    +      } else {
    
    546
    +        // no settings available for country
    
    547
    +        this.#throwError(
    
    548
    +          TorStrings.torConnect.autoBootstrappingFailed,
    
    549
    +          TorStrings.torConnect.noSettingsForCountry
    
    550
    +        );
    
    551
    +      }
    
    552
    +    }
    
    553
    +  }
    
    554
    +
    
    555
    +  /**
    
    556
    +   * Try to apply the settings we fetched.
    
    557
    +   */
    
    558
    +  async #trySettings() {
    
    559
    +    // Otherwise, apply each of our settings and try to bootstrap with each.
    
    560
    +    for (const [index, currentSetting] of this.#settings.entries()) {
    
    561
    +      if (this.transitioning) {
    
    562
    +        break;
    
    563
    +      }
    
    564
    +
    
    565
    +      lazy.logger.info(
    
    566
    +        `Attempting Bootstrap with configuration ${index + 1}/${
    
    567
    +          this.#settings.length
    
    568
    +        }`
    
    569
    +      );
    
    570
    +
    
    571
    +      // Send the new settings directly to the provider. We will save them only
    
    572
    +      // if the bootstrap succeeds.
    
    573
    +      // FIXME: We should somehow signal TorSettings users that we have set
    
    574
    +      // custom settings, and they should not apply theirs until we are done
    
    575
    +      // with trying ours.
    
    576
    +      // Otherwise, the new settings provided by the user while we were
    
    577
    +      // bootstrapping could be the ones that cause the bootstrap to succeed,
    
    578
    +      // but we overwrite them (unless we backup the original settings, and then
    
    579
    +      // save our new settings only if they have not changed).
    
    580
    +      // Another idea (maybe easier to implement) is to disable the settings
    
    581
    +      // UI while *any* bootstrap is going on.
    
    582
    +      // This is also documented in tor-browser#41921.
    
    583
    +      const provider = await lazy.TorProviderBuilder.build();
    
    584
    +      this.#changedSettings = true;
    
    585
    +      // We need to merge with old settings, in case the user is using a proxy
    
    586
    +      // or is behind a firewall.
    
    587
    +      await provider.writeSettings({
    
    588
    +        ...TorSettings.getSettings(),
    
    589
    +        ...currentSetting,
    
    590
    +      });
    
    591
    +
    
    592
    +      // Build out our bootstrap request.
    
    593
    +      const bootstrap = new lazy.TorBootstrapRequest();
    
    594
    +      bootstrap.onbootstrapstatus = (progress, status) => {
    
    595
    +        TorConnect._updateBootstrapStatus(progress, status);
    
    596
    +      };
    
    597
    +      bootstrap.onbootstraperror = (message, details) => {
    
    598
    +        lazy.logger.error(`Auto-Bootstrap error => ${message}; ${details}`);
    
    599
    +      };
    
    600
    +
    
    601
    +      // Begin the bootstrap.
    
    602
    +      const success = await Promise.race([
    
    603
    +        bootstrap.bootstrap(),
    
    604
    +        this.#transitionPromise,
    
    605
    +      ]);
    
    606
    +      // Either the bootstrap request has finished, or a transition (caused by
    
    607
    +      // an error or by user's cancelation) started.
    
    608
    +      // However, we cannot be already transitioning in case of success, so if
    
    609
    +      // we are we should cancel the current bootstrap.
    
    610
    +      // With the current TorProvider, this will set DisableNetwork=1 again,
    
    611
    +      // which is what the user wanted if they canceled.
    
    612
    +      if (this.transitioning) {
    
    613
    +        if (success) {
    
    614
    +          lazy.logger.warn(
    
    615
    +            "We were already transitioning after a success, we were not expecting this."
    
    616
    +          );
    
    617
    +        }
    
    618
    +        bootstrap.cancel();
    
    619
    +        return;
    
    620
    +      }
    
    621
    +      if (success) {
    
    622
    +        // Persist the current settings to preferences.
    
    623
    +        TorSettings.setSettings(currentSetting);
    
    624
    +        TorSettings.saveToPrefs();
    
    625
    +        // Do not await `applySettings`. Otherwise this opens up a window of
    
    626
    +        // time where the user can still "Cancel" the bootstrap.
    
    627
    +        // We are calling `applySettings` just to be on the safe side, but the
    
    628
    +        // settings we are passing now should be exactly the same we already
    
    629
    +        // passed earlier.
    
    630
    +        TorSettings.applySettings().catch(e =>
    
    631
    +          lazy.logger.error("TorSettings.applySettings threw unexpectedly.", e)
    
    632
    +        );
    
    633
    +        this.changeState(TorConnectState.Bootstrapped);
    
    634
    +        return;
    
    635
    +      }
    
    636
    +    }
    
    637
    +
    
    638
    +    // Only explicitly change state here if something else has not transitioned
    
    639
    +    // us.
    
    640
    +    if (!this.transitioning) {
    
    641
    +      this.#throwError(
    
    642
    +        TorStrings.torConnect.autoBootstrappingFailed,
    
    643
    +        TorStrings.torConnect.autoBootstrappingAllFailed
    
    644
    +      );
    
    645
    +    }
    
    646
    +  }
    
    647
    +
    
    648
    +  #throwError(message, details) {
    
    649
    +    let err = new Error(message);
    
    650
    +    err.details = details;
    
    651
    +    throw err;
    
    652
    +  }
    
    653
    +
    
    654
    +  transitionRequested() {
    
    655
    +    this.#transitionResolve();
    
    656
    +  }
    
    657
    +
    
    658
    +  async cleanup(nextState) {
    
    659
    +    // No need to await.
    
    660
    +    this.#moat?.uninit();
    
    661
    +    this.#moat = null;
    
    662
    +
    
    663
    +    if (this.#changedSettings && nextState !== TorConnectState.Bootstrapped) {
    
    664
    +      try {
    
    665
    +        await TorSettings.applySettings();
    
    666
    +      } catch (e) {
    
    667
    +        // We cannot do much if the original settings were bad or
    
    668
    +        // if the connection closed, so just report it in the
    
    669
    +        // console.
    
    670
    +        lazy.logger.warn("Failed to restore original settings.", e);
    
    671
    +      }
    
    672
    +    }
    
    673
    +  }
    
    674
    +}
    
    675
    +
    
    676
    +class BootstrappedState extends StateCallback {
    
    677
    +  // We may need to leave the bootstrapped state if the tor daemon
    
    678
    +  // exits (if it is restarted, we will have to bootstrap again).
    
    679
    +  allowedTransitions = Object.freeze([TorConnectState.Configuring]);
    
    680
    +
    
    681
    +  constructor() {
    
    682
    +    super(TorConnectState.Bootstrapped);
    
    683
    +  }
    
    684
    +
    
    685
    +  run() {
    
    686
    +    // Notify observers of bootstrap completion.
    
    687
    +    Services.obs.notifyObservers(null, TorConnectTopics.BootstrapComplete);
    
    688
    +  }
    
    689
    +}
    
    690
    +
    
    691
    +class ErrorState extends StateCallback {
    
    692
    +  allowedTransitions = Object.freeze([TorConnectState.Configuring]);
    
    693
    +
    
    694
    +  static #hasEverHappened = false;
    
    695
    +
    
    696
    +  constructor() {
    
    697
    +    super(TorConnectState.Error);
    
    698
    +    ErrorState.#hasEverHappened = true;
    
    699
    +  }
    
    700
    +
    
    701
    +  run(errorMessage, errorDetails) {
    
    702
    +    TorConnect._errorMessage = errorMessage;
    
    703
    +    TorConnect._errorDetails = errorDetails;
    
    704
    +    lazy.logger.error(
    
    705
    +      `Entering error state (${errorMessage}, ${errorDetails})`
    
    706
    +    );
    
    707
    +
    
    708
    +    Services.obs.notifyObservers(
    
    709
    +      { message: errorMessage, details: errorDetails },
    
    710
    +      TorConnectTopics.BootstrapError
    
    711
    +    );
    
    712
    +
    
    713
    +    this.changeState(TorConnectState.Configuring);
    
    714
    +  }
    
    715
    +
    
    716
    +  static get hasEverHappened() {
    
    717
    +    return ErrorState.#hasEverHappened;
    
    718
    +  }
    
    719
    +}
    
    720
    +
    
    721
    +class DisabledState extends StateCallback {
    
    722
    +  // Trap state: no way to leave the Disabled state.
    
    723
    +  allowedTransitions = Object.freeze([]);
    
    724
    +
    
    725
    +  constructor() {
    
    726
    +    super(TorConnectState.DisabledState);
    
    727
    +  }
    
    728
    +
    
    729
    +  async run() {
    
    730
    +    lazy.logger.debug("Entered the disabled state.");
    
    731
    +  }
    
    732
    +}
    
    733
    +
    
    249 734
     export const InternetStatus = Object.freeze({
    
    250 735
       Unknown: -1,
    
    251 736
       Offline: 0,
    
    ... ... @@ -253,1005 +738,581 @@ export const InternetStatus = Object.freeze({
    253 738
     });
    
    254 739
     
    
    255 740
     class InternetTest {
    
    741
    +  #enabled;
    
    742
    +  #status = InternetStatus.Unknown;
    
    743
    +  #error = null;
    
    744
    +  #pending = false;
    
    745
    +  #canceled = false;
    
    746
    +  #timeout = 0;
    
    747
    +
    
    256 748
       constructor() {
    
    257
    -    this._enabled = Services.prefs.getBoolPref(
    
    749
    +    this.#enabled = Services.prefs.getBoolPref(
    
    258 750
           TorConnectPrefs.allow_internet_test,
    
    259 751
           true
    
    260 752
         );
    
    261
    -
    
    262
    -    this._status = InternetStatus.Unknown;
    
    263
    -    this._error = null;
    
    264
    -    this._pending = false;
    
    265
    -    if (this._enabled) {
    
    266
    -      this._timeout = setTimeout(() => {
    
    267
    -        this._timeout = null;
    
    753
    +    if (this.#enabled) {
    
    754
    +      this.#timeout = setTimeout(() => {
    
    755
    +        this.#timeout = 0;
    
    268 756
             this.test();
    
    269
    -      }, this.timeoutRand());
    
    757
    +      }, this.#timeoutRand());
    
    270 758
         }
    
    271
    -    this.onResult = (online, date) => {};
    
    759
    +    this.onResult = online => {};
    
    272 760
         this.onError = err => {};
    
    273 761
       }
    
    274 762
     
    
    275
    -  test() {
    
    276
    -    if (this._pending || !this._enabled) {
    
    763
    +  /**
    
    764
    +   * Perform the internet test.
    
    765
    +   *
    
    766
    +   * While this is an async method, the callers are not expected to await it,
    
    767
    +   * as we are also using callbacks.
    
    768
    +   */
    
    769
    +  async test() {
    
    770
    +    if (this.#pending || !this.#enabled) {
    
    277 771
           return;
    
    278 772
         }
    
    279 773
         this.cancel();
    
    280
    -    this._pending = true;
    
    774
    +    this.#pending = true;
    
    775
    +    this.#canceled = false;
    
    281 776
     
    
    282 777
         lazy.logger.info("Starting the Internet test");
    
    283
    -    this._testAsync()
    
    284
    -      .then(status => {
    
    285
    -        this._pending = false;
    
    286
    -        this._status = status.successful
    
    287
    -          ? InternetStatus.Online
    
    288
    -          : InternetStatus.Offline;
    
    289
    -        lazy.logger.info(`Performed Internet test, outcome ${this._status}`);
    
    290
    -        this.onResult(this.status, status.date);
    
    291
    -      })
    
    292
    -      .catch(error => {
    
    293
    -        this._error = error;
    
    294
    -        this._pending = false;
    
    295
    -        this.onError(error);
    
    296
    -      });
    
    297
    -  }
    
    298
    -
    
    299
    -  cancel() {
    
    300
    -    if (this._timeout !== null) {
    
    301
    -      clearTimeout(this._timeout);
    
    302
    -      this._timeout = null;
    
    303
    -    }
    
    304
    -  }
    
    305
    -
    
    306
    -  async _testAsync() {
    
    307
    -    // Callbacks for the Internet test are desirable, because we will be
    
    308
    -    // waiting both for the bootstrap, and for the Internet test.
    
    309
    -    // However, managing Moat with async/await is much easier as it avoids a
    
    310
    -    // callback hell, and it makes extra explicit that we are uniniting it.
    
    311 778
         const mrpc = new lazy.MoatRPC();
    
    312
    -    let status = null;
    
    313
    -    let error = null;
    
    314 779
         try {
    
    315 780
           await mrpc.init();
    
    316
    -      status = await mrpc.testInternetConnection();
    
    781
    +      const status = await mrpc.testInternetConnection();
    
    782
    +      this.#status = status.successful
    
    783
    +        ? InternetStatus.Online
    
    784
    +        : InternetStatus.Offline;
    
    785
    +      // TODO: We could consume the date we got from the HTTP request to detect
    
    786
    +      // big clock skews that might prevent a successfull bootstrap.
    
    787
    +      lazy.logger.info(`Performed Internet test, outcome ${this.#status}`);
    
    317 788
         } catch (err) {
    
    318 789
           lazy.logger.error("Error while checking the Internet connection", err);
    
    319
    -      error = err;
    
    790
    +      this.#error = err;
    
    791
    +      this.#pending = false;
    
    320 792
         } finally {
    
    321 793
           mrpc.uninit();
    
    322 794
         }
    
    323
    -    if (error !== null) {
    
    324
    -      throw error;
    
    795
    +
    
    796
    +    if (this.#canceled) {
    
    797
    +      return;
    
    798
    +    }
    
    799
    +    if (this.#error) {
    
    800
    +      this.onError(this.#error);
    
    801
    +    } else {
    
    802
    +      this.onResult(this.#status);
    
    803
    +    }
    
    804
    +  }
    
    805
    +
    
    806
    +  cancel() {
    
    807
    +    this.#canceled = true;
    
    808
    +    if (this.#timeout) {
    
    809
    +      clearTimeout(this.#timeout);
    
    810
    +      this.#timeout = 0;
    
    325 811
         }
    
    326
    -    return status;
    
    327 812
       }
    
    328 813
     
    
    329 814
       get status() {
    
    330
    -    return this._status;
    
    815
    +    return this.#status;
    
    331 816
       }
    
    332 817
     
    
    333 818
       get error() {
    
    334
    -    return this._error;
    
    819
    +    return this.#error;
    
    335 820
       }
    
    336 821
     
    
    337 822
       get enabled() {
    
    338
    -    return this._enabled;
    
    823
    +    return this.#enabled;
    
    339 824
       }
    
    340 825
     
    
    341
    -  // We randomize the Internet test timeout to make fingerprinting it harder, at least a little bit...
    
    342
    -  timeoutRand() {
    
    826
    +  // We randomize the Internet test timeout to make fingerprinting it harder, at
    
    827
    +  // least a little bit...
    
    828
    +  #timeoutRand() {
    
    343 829
         const offset = 30000;
    
    344 830
         const randRange = 5000;
    
    345 831
         return offset + randRange * (Math.random() * 2 - 1);
    
    346 832
       }
    
    347 833
     }
    
    348 834
     
    
    349
    -export const TorConnect = (() => {
    
    350
    -  let retval = {
    
    351
    -    _state: TorConnectState.Initial,
    
    352
    -    _bootstrapProgress: 0,
    
    353
    -    _bootstrapStatus: null,
    
    354
    -    _internetStatus: InternetStatus.Unknown,
    
    355
    -    // list of country codes Moat has settings for
    
    356
    -    _countryCodes: [],
    
    357
    -    _countryNames: Object.freeze(
    
    358
    -      (() => {
    
    359
    -        const codes = Services.intl.getAvailableLocaleDisplayNames("region");
    
    360
    -        const names = Services.intl.getRegionDisplayNames(undefined, codes);
    
    361
    -        let codesNames = {};
    
    362
    -        for (let i = 0; i < codes.length; i++) {
    
    363
    -          codesNames[codes[i]] = names[i];
    
    364
    -        }
    
    365
    -        return codesNames;
    
    366
    -      })()
    
    367
    -    ),
    
    368
    -    _detectedLocation: "",
    
    369
    -    _errorMessage: null,
    
    370
    -    _errorDetails: null,
    
    371
    -    _logHasWarningOrError: false,
    
    372
    -    _hasEverFailed: false,
    
    373
    -    _hasBootstrapEverFailed: false,
    
    374
    -    _transitionPromise: null,
    
    375
    -
    
    376
    -    // This is used as a helper to make the state of about:torconnect persistent
    
    377
    -    // during a session, but TorConnect does not use this data at all.
    
    378
    -    _uiState: {},
    
    379
    -
    
    380
    -    /* These functions represent ongoing work associated with one of our states
    
    381
    -           Some of these functions are mostly empty, apart from defining an
    
    382
    -           on_transition function used to resolve their Promise */
    
    383
    -    _stateCallbacks: Object.freeze(
    
    384
    -      new Map([
    
    385
    -        /* Initial is never transitioned to */
    
    386
    -        [
    
    387
    -          TorConnectState.Initial,
    
    388
    -          new StateCallback(TorConnectState.Initial, async function () {
    
    389
    -            // The initial state doesn't actually do anything, so here is a skeleton for other
    
    390
    -            // states which do perform work
    
    391
    -            await new Promise(async (resolve, reject) => {
    
    392
    -              // This function is provided to signal to the callback that it is complete.
    
    393
    -              // It is called as a result of _changeState and at the very least must
    
    394
    -              // resolve the root Promise object within the StateCallback function
    
    395
    -              // The on_transition callback may also perform necessary cleanup work
    
    396
    -              this.on_transition = nextState => {
    
    397
    -                resolve();
    
    398
    -              };
    
    399
    -
    
    400
    -              try {
    
    401
    -                // each state may have a sequence of async work to do
    
    402
    -                let asyncWork = async () => {};
    
    403
    -                await asyncWork();
    
    404
    -
    
    405
    -                // after each block we may check for an opportunity to early-out
    
    406
    -                if (this.transitioning) {
    
    407
    -                  return;
    
    408
    -                }
    
    409
    -
    
    410
    -                // repeat the above pattern as necessary
    
    411
    -              } catch (err) {
    
    412
    -                // any thrown exceptions here will trigger a transition to the Error state
    
    413
    -                TorConnect._changeState(
    
    414
    -                  TorConnectState.Error,
    
    415
    -                  err?.message,
    
    416
    -                  err?.details
    
    417
    -                );
    
    418
    -              }
    
    419
    -            });
    
    420
    -          }),
    
    421
    -        ],
    
    422
    -        /* Configuring */
    
    423
    -        [
    
    424
    -          TorConnectState.Configuring,
    
    425
    -          new StateCallback(TorConnectState.Configuring, async function () {
    
    426
    -            await new Promise(async (resolve, reject) => {
    
    427
    -              this.on_transition = nextState => {
    
    428
    -                resolve();
    
    429
    -              };
    
    430
    -            });
    
    431
    -          }),
    
    432
    -        ],
    
    433
    -        /* Bootstrapping */
    
    434
    -        [
    
    435
    -          TorConnectState.Bootstrapping,
    
    436
    -          new StateCallback(TorConnectState.Bootstrapping, async function () {
    
    437
    -            // wait until bootstrap completes or we get an error
    
    438
    -            await new Promise(async (resolve, reject) => {
    
    439
    -              // debug hook to simulate censorship preventing bootstrapping
    
    440
    -              if (
    
    441
    -                Services.prefs.getIntPref(TorConnectPrefs.censorship_level, 0) >
    
    442
    -                0
    
    443
    -              ) {
    
    444
    -                this.on_transition = nextState => {
    
    445
    -                  resolve();
    
    446
    -                };
    
    447
    -                await debug_sleep(1500);
    
    448
    -                TorConnect._hasBootstrapEverFailed = true;
    
    449
    -                if (
    
    450
    -                  Services.prefs.getIntPref(
    
    451
    -                    TorConnectPrefs.censorship_level,
    
    452
    -                    0
    
    453
    -                  ) === 2
    
    454
    -                ) {
    
    455
    -                  const codes = Object.keys(TorConnect._countryNames);
    
    456
    -                  TorConnect._detectedLocation =
    
    457
    -                    codes[Math.floor(Math.random() * codes.length)];
    
    458
    -                }
    
    459
    -                TorConnect._changeState(
    
    460
    -                  TorConnectState.Error,
    
    461
    -                  "Bootstrap failed (for debugging purposes)",
    
    462
    -                  "Error: Censorship simulation",
    
    463
    -                  true
    
    464
    -                );
    
    465
    -                return;
    
    466
    -              }
    
    467
    -
    
    468
    -              const tbr = new lazy.TorBootstrapRequest();
    
    469
    -              const internetTest = new InternetTest();
    
    470
    -              let cancelled = false;
    
    471
    -
    
    472
    -              let bootstrapError = "";
    
    473
    -              let bootstrapErrorDetails = "";
    
    474
    -              const maybeTransitionToError = () => {
    
    475
    -                if (
    
    476
    -                  internetTest.status === InternetStatus.Unknown &&
    
    477
    -                  internetTest.error === null &&
    
    478
    -                  internetTest.enabled
    
    479
    -                ) {
    
    480
    -                  // We have been called by a failed bootstrap, but the internet test has not run yet - force
    
    481
    -                  // it to run immediately!
    
    482
    -                  internetTest.test();
    
    483
    -                  // Return from this call, because the Internet test's callback will call us again
    
    484
    -                  return;
    
    485
    -                }
    
    486
    -                // Do not transition to the offline error until we are sure that also the bootstrap failed, in
    
    487
    -                // case Moat is down but the bootstrap can proceed anyway.
    
    488
    -                if (bootstrapError === "") {
    
    489
    -                  return;
    
    490
    -                }
    
    491
    -                if (internetTest.status === InternetStatus.Offline) {
    
    492
    -                  TorConnect._changeState(
    
    493
    -                    TorConnectState.Error,
    
    494
    -                    TorStrings.torConnect.offline,
    
    495
    -                    "",
    
    496
    -                    true
    
    497
    -                  );
    
    498
    -                } else {
    
    499
    -                  // Give priority to the bootstrap error, in case the Internet test fails
    
    500
    -                  TorConnect._hasBootstrapEverFailed = true;
    
    501
    -                  TorConnect._changeState(
    
    502
    -                    TorConnectState.Error,
    
    503
    -                    bootstrapError,
    
    504
    -                    bootstrapErrorDetails,
    
    505
    -                    true
    
    506
    -                  );
    
    507
    -                }
    
    508
    -              };
    
    509
    -
    
    510
    -              this.on_transition = async nextState => {
    
    511
    -                if (nextState === TorConnectState.Configuring) {
    
    512
    -                  // stop bootstrap process if user cancelled
    
    513
    -                  cancelled = true;
    
    514
    -                  internetTest.cancel();
    
    515
    -                  await tbr.cancel();
    
    516
    -                }
    
    517
    -                resolve();
    
    518
    -              };
    
    519
    -
    
    520
    -              tbr.onbootstrapstatus = (progress, status) => {
    
    521
    -                TorConnect._updateBootstrapStatus(progress, status);
    
    522
    -              };
    
    523
    -              tbr.onbootstrapcomplete = () => {
    
    524
    -                internetTest.cancel();
    
    525
    -                TorConnect._changeState(TorConnectState.Bootstrapped);
    
    526
    -              };
    
    527
    -              tbr.onbootstraperror = (message, details) => {
    
    528
    -                if (cancelled) {
    
    529
    -                  // We ignore this error since it occurred after cancelling (by
    
    530
    -                  // the user). We assume the error is just a side effect of the
    
    531
    -                  // cancelling.
    
    532
    -                  // E.g. If the cancelling is triggered late in the process, we
    
    533
    -                  // get "Building circuits: Establishing a Tor circuit failed".
    
    534
    -                  // TODO: Maybe move this logic deeper in the process to know
    
    535
    -                  // when to filter out such errors triggered by cancelling.
    
    536
    -                  lazy.logger.warn(
    
    537
    -                    `Post-cancel error => ${message}; ${details}`
    
    538
    -                  );
    
    539
    -                  return;
    
    540
    -                }
    
    541
    -                // We have to wait for the Internet test to finish before sending the bootstrap error
    
    542
    -                bootstrapError = message;
    
    543
    -                bootstrapErrorDetails = details;
    
    544
    -                maybeTransitionToError();
    
    545
    -              };
    
    546
    -
    
    547
    -              internetTest.onResult = (status, date) => {
    
    548
    -                // TODO: Use the date to save the clock skew?
    
    549
    -                TorConnect._internetStatus = status;
    
    550
    -                maybeTransitionToError();
    
    551
    -              };
    
    552
    -              internetTest.onError = () => {
    
    553
    -                maybeTransitionToError();
    
    554
    -              };
    
    555
    -
    
    556
    -              tbr.bootstrap();
    
    557
    -            });
    
    558
    -          }),
    
    559
    -        ],
    
    560
    -        /* AutoBootstrapping */
    
    561
    -        [
    
    562
    -          TorConnectState.AutoBootstrapping,
    
    563
    -          new StateCallback(TorConnectState.AutoBootstrapping, async function (
    
    564
    -            countryCode
    
    565
    -          ) {
    
    566
    -            await new Promise(async (resolve, reject) => {
    
    567
    -              this.on_transition = nextState => {
    
    568
    -                resolve();
    
    569
    -              };
    
    570
    -
    
    571
    -              // debug hook to simulate censorship preventing bootstrapping
    
    572
    -              {
    
    573
    -                const censorshipLevel = Services.prefs.getIntPref(
    
    574
    -                  TorConnectPrefs.censorship_level,
    
    575
    -                  0
    
    576
    -                );
    
    577
    -                if (censorshipLevel > 1) {
    
    578
    -                  this.on_transition = nextState => {
    
    579
    -                    resolve();
    
    580
    -                  };
    
    581
    -                  // always fail even after manually selecting location specific settings
    
    582
    -                  if (censorshipLevel == 3) {
    
    583
    -                    await debug_sleep(2500);
    
    584
    -                    TorConnect._changeState(
    
    585
    -                      TorConnectState.Error,
    
    586
    -                      "Error: censorship simulation",
    
    587
    -                      "",
    
    588
    -                      true
    
    589
    -                    );
    
    590
    -                    return;
    
    591
    -                    // only fail after auto selecting, manually selecting succeeds
    
    592
    -                  } else if (censorshipLevel == 2 && !countryCode) {
    
    593
    -                    await debug_sleep(2500);
    
    594
    -                    TorConnect._changeState(
    
    595
    -                      TorConnectState.Error,
    
    596
    -                      "Error: Severe Censorship simulation",
    
    597
    -                      "",
    
    598
    -                      true
    
    599
    -                    );
    
    600
    -                    return;
    
    601
    -                  }
    
    602
    -                }
    
    603
    -              }
    
    604
    -
    
    605
    -              const throw_error = (message, details) => {
    
    606
    -                let err = new Error(message);
    
    607
    -                err.details = details;
    
    608
    -                throw err;
    
    609
    -              };
    
    610
    -
    
    611
    -              // lookup user's potential censorship circumvention settings from Moat service
    
    612
    -              try {
    
    613
    -                this.mrpc = new lazy.MoatRPC();
    
    614
    -                await this.mrpc.init();
    
    615
    -
    
    616
    -                if (this.transitioning) {
    
    617
    -                  return;
    
    618
    -                }
    
    619
    -
    
    620
    -                const settings = await this.mrpc.circumvention_settings(
    
    621
    -                  [...TorSettings.builtinBridgeTypes, "vanilla"],
    
    622
    -                  countryCode
    
    623
    -                );
    
    624
    -
    
    625
    -                if (this.transitioning) {
    
    626
    -                  return;
    
    627
    -                }
    
    628
    -
    
    629
    -                if (settings?.country) {
    
    630
    -                  TorConnect._detectedLocation = settings.country;
    
    631
    -                }
    
    632
    -                if (settings?.settings && settings.settings.length) {
    
    633
    -                  this.settings = settings.settings;
    
    634
    -                } else {
    
    635
    -                  try {
    
    636
    -                    this.settings = await this.mrpc.circumvention_defaults([
    
    637
    -                      ...TorSettings.builtinBridgeTypes,
    
    638
    -                      "vanilla",
    
    639
    -                    ]);
    
    640
    -                  } catch (err) {
    
    641
    -                    lazy.logger.error(
    
    642
    -                      "We did not get localized settings, and default settings failed as well",
    
    643
    -                      err
    
    644
    -                    );
    
    645
    -                  }
    
    646
    -                }
    
    647
    -                if (this.settings === null || this.settings.length === 0) {
    
    648
    -                  // The fallback has failed as well, so throw the original error
    
    649
    -                  if (!TorConnect._detectedLocation) {
    
    650
    -                    // unable to determine country
    
    651
    -                    throw_error(
    
    652
    -                      TorStrings.torConnect.autoBootstrappingFailed,
    
    653
    -                      TorStrings.torConnect.cannotDetermineCountry
    
    654
    -                    );
    
    655
    -                  } else {
    
    656
    -                    // no settings available for country
    
    657
    -                    throw_error(
    
    658
    -                      TorStrings.torConnect.autoBootstrappingFailed,
    
    659
    -                      TorStrings.torConnect.noSettingsForCountry
    
    660
    -                    );
    
    661
    -                  }
    
    662
    -                }
    
    663
    -
    
    664
    -                const restoreOriginalSettings = async () => {
    
    665
    -                  try {
    
    666
    -                    await TorSettings.applySettings();
    
    667
    -                  } catch (e) {
    
    668
    -                    // We cannot do much if the original settings were bad or
    
    669
    -                    // if the connection closed, so just report it in the
    
    670
    -                    // console.
    
    671
    -                    lazy.logger.warn("Failed to restore original settings.", e);
    
    672
    -                  }
    
    673
    -                };
    
    674
    -
    
    675
    -                // apply each of our settings and try to bootstrap with each
    
    676
    -                try {
    
    677
    -                  for (const [
    
    678
    -                    index,
    
    679
    -                    currentSetting,
    
    680
    -                  ] of this.settings.entries()) {
    
    681
    -                    // we want to break here so we can fall through and restore original settings
    
    682
    -                    if (this.transitioning) {
    
    683
    -                      break;
    
    684
    -                    }
    
    685
    -
    
    686
    -                    lazy.logger.info(
    
    687
    -                      `Attempting Bootstrap with configuration ${index + 1}/${
    
    688
    -                        this.settings.length
    
    689
    -                      }`
    
    690
    -                    );
    
    691
    -
    
    692
    -                    // Send the new settings directly to the provider. We will
    
    693
    -                    // save them only if the bootstrap succeeds.
    
    694
    -                    // FIXME: We should somehow signal TorSettings users that we
    
    695
    -                    // have set custom settings, and they should not apply
    
    696
    -                    // theirs until we are done with trying ours.
    
    697
    -                    // Otherwise, the new settings provided by the user while we
    
    698
    -                    // were bootstrapping could be the ones that cause the
    
    699
    -                    // bootstrap to succeed, but we overwrite them (unless we
    
    700
    -                    // backup the original settings, and then save our new
    
    701
    -                    // settings only if they have not changed).
    
    702
    -                    // Another idea (maybe easier to implement) is to disable
    
    703
    -                    // the settings UI while *any* bootstrap is going on.
    
    704
    -                    // This is also documented in tor-browser#41921.
    
    705
    -                    const provider = await lazy.TorProviderBuilder.build();
    
    706
    -                    // We need to merge with old settings, in case the user is
    
    707
    -                    // using a proxy or is behind a firewall.
    
    708
    -                    await provider.writeSettings({
    
    709
    -                      ...TorSettings.getSettings(),
    
    710
    -                      ...currentSetting,
    
    711
    -                    });
    
    712
    -
    
    713
    -                    // build out our bootstrap request
    
    714
    -                    const tbr = new lazy.TorBootstrapRequest();
    
    715
    -                    tbr.onbootstrapstatus = (progress, status) => {
    
    716
    -                      TorConnect._updateBootstrapStatus(progress, status);
    
    717
    -                    };
    
    718
    -                    tbr.onbootstraperror = (message, details) => {
    
    719
    -                      lazy.logger.error(
    
    720
    -                        `Auto-Bootstrap error => ${message}; ${details}`
    
    721
    -                      );
    
    722
    -                    };
    
    723
    -
    
    724
    -                    // update transition callback for user cancel
    
    725
    -                    this.on_transition = async nextState => {
    
    726
    -                      if (nextState === TorConnectState.Configuring) {
    
    727
    -                        await tbr.cancel();
    
    728
    -                        await restoreOriginalSettings();
    
    729
    -                      }
    
    730
    -                      resolve();
    
    731
    -                    };
    
    732
    -
    
    733
    -                    // begin bootstrap
    
    734
    -                    if (await tbr.bootstrap()) {
    
    735
    -                      // persist the current settings to preferences
    
    736
    -                      TorSettings.setSettings(currentSetting);
    
    737
    -                      TorSettings.saveToPrefs();
    
    738
    -                      await TorSettings.applySettings();
    
    739
    -                      TorConnect._changeState(TorConnectState.Bootstrapped);
    
    740
    -                      return;
    
    741
    -                    }
    
    742
    -                  }
    
    743
    -
    
    744
    -                  // Bootstrap failed for all potential settings, so restore the
    
    745
    -                  // original settings the provider.
    
    746
    -                  await restoreOriginalSettings();
    
    747
    -
    
    748
    -                  // Only explicitly change state here if something else has not
    
    749
    -                  // transitioned us.
    
    750
    -                  if (!this.transitioning) {
    
    751
    -                    throw_error(
    
    752
    -                      TorStrings.torConnect.autoBootstrappingFailed,
    
    753
    -                      TorStrings.torConnect.autoBootstrappingAllFailed
    
    754
    -                    );
    
    755
    -                  }
    
    756
    -                  return;
    
    757
    -                } catch (err) {
    
    758
    -                  await restoreOriginalSettings();
    
    759
    -                  // throw to outer catch to transition us.
    
    760
    -                  throw err;
    
    761
    -                }
    
    762
    -              } catch (err) {
    
    763
    -                if (this.mrpc?.inited) {
    
    764
    -                  // lookup countries which have settings available
    
    765
    -                  TorConnect._countryCodes =
    
    766
    -                    await this.mrpc.circumvention_countries();
    
    767
    -                }
    
    768
    -                if (!this.transitioning) {
    
    769
    -                  TorConnect._changeState(
    
    770
    -                    TorConnectState.Error,
    
    771
    -                    err?.message,
    
    772
    -                    err?.details,
    
    773
    -                    true
    
    774
    -                  );
    
    775
    -                } else {
    
    776
    -                  lazy.logger.error(
    
    777
    -                    "Received AutoBootstrapping error after transitioning",
    
    778
    -                    err
    
    779
    -                  );
    
    780
    -                }
    
    781
    -              } finally {
    
    782
    -                // important to uninit MoatRPC object or else the pt process will live as long as tor-browser
    
    783
    -                this.mrpc?.uninit();
    
    784
    -              }
    
    785
    -            });
    
    786
    -          }),
    
    787
    -        ],
    
    788
    -        /* Bootstrapped */
    
    789
    -        [
    
    790
    -          TorConnectState.Bootstrapped,
    
    791
    -          new StateCallback(TorConnectState.Bootstrapped, async function () {
    
    792
    -            await new Promise((resolve, reject) => {
    
    793
    -              // We may need to leave the bootstrapped state if the tor daemon
    
    794
    -              // exits (if it is restarted, we will have to bootstrap again).
    
    795
    -              this.on_transition = nextState => {
    
    796
    -                resolve();
    
    797
    -              };
    
    798
    -              // notify observers of bootstrap completion
    
    799
    -              Services.obs.notifyObservers(
    
    800
    -                null,
    
    801
    -                TorConnectTopics.BootstrapComplete
    
    802
    -              );
    
    803
    -            });
    
    804
    -          }),
    
    805
    -        ],
    
    806
    -        /* Error */
    
    807
    -        [
    
    808
    -          TorConnectState.Error,
    
    809
    -          new StateCallback(TorConnectState.Error, async function (
    
    810
    -            errorMessage,
    
    811
    -            errorDetails,
    
    812
    -            bootstrappingFailure
    
    813
    -          ) {
    
    814
    -            await new Promise((resolve, reject) => {
    
    815
    -              this.on_transition = async nextState => {
    
    816
    -                resolve();
    
    817
    -              };
    
    818
    -
    
    819
    -              TorConnect._errorMessage = errorMessage;
    
    820
    -              TorConnect._errorDetails = errorDetails;
    
    821
    -              lazy.logger.error(
    
    822
    -                `Entering error state (${errorMessage}, ${errorDetails})`
    
    823
    -              );
    
    824
    -
    
    825
    -              Services.obs.notifyObservers(
    
    826
    -                { message: errorMessage, details: errorDetails },
    
    827
    -                TorConnectTopics.BootstrapError
    
    828
    -              );
    
    829
    -
    
    830
    -              TorConnect._changeState(TorConnectState.Configuring);
    
    831
    -            });
    
    832
    -          }),
    
    833
    -        ],
    
    834
    -        /* Disabled */
    
    835
    -        [
    
    836
    -          TorConnectState.Disabled,
    
    837
    -          new StateCallback(TorConnectState.Disabled, async function () {
    
    838
    -            await new Promise((resolve, reject) => {
    
    839
    -              // no-op, on_transition not defined because no way to leave Disabled state
    
    840
    -            });
    
    841
    -          }),
    
    842
    -        ],
    
    843
    -      ])
    
    844
    -    ),
    
    845
    -
    
    846
    -    _callback(state) {
    
    847
    -      return this._stateCallbacks.get(state);
    
    848
    -    },
    
    849
    -
    
    850
    -    _changeState(newState, ...args) {
    
    851
    -      if (newState === TorConnectState.Error) {
    
    852
    -        this._hasEverFailed = true;
    
    835
    +export const TorConnect = {
    
    836
    +  _stateHandler: new InitialState(),
    
    837
    +  _bootstrapProgress: 0,
    
    838
    +  _bootstrapStatus: null,
    
    839
    +  _internetStatus: InternetStatus.Unknown,
    
    840
    +  // list of country codes Moat has settings for
    
    841
    +  _countryCodes: [],
    
    842
    +  _countryNames: Object.freeze(
    
    843
    +    (() => {
    
    844
    +      const codes = Services.intl.getAvailableLocaleDisplayNames("region");
    
    845
    +      const names = Services.intl.getRegionDisplayNames(undefined, codes);
    
    846
    +      let codesNames = {};
    
    847
    +      for (let i = 0; i < codes.length; i++) {
    
    848
    +        codesNames[codes[i]] = names[i];
    
    853 849
           }
    
    854
    -      const prevState = this._state;
    
    850
    +      return codesNames;
    
    851
    +    })()
    
    852
    +  ),
    
    853
    +  _detectedLocation: "",
    
    854
    +  _errorMessage: null,
    
    855
    +  _errorDetails: null,
    
    856
    +  _logHasWarningOrError: false,
    
    857
    +  _hasBootstrapEverFailed: false,
    
    858
    +  _transitionPromise: null,
    
    859
    +
    
    860
    +  // This is used as a helper to make the state of about:torconnect persistent
    
    861
    +  // during a session, but TorConnect does not use this data at all.
    
    862
    +  _uiState: {},
    
    863
    +
    
    864
    +  _stateCallbacks: Object.freeze(
    
    865
    +    new Map([
    
    866
    +      // Initial is never transitioned to
    
    867
    +      [TorConnectState.Initial, InitialState],
    
    868
    +      [TorConnectState.Configuring, ConfiguringState],
    
    869
    +      [TorConnectState.Bootstrapping, BootstrappingState],
    
    870
    +      [TorConnectState.AutoBootstrapping, AutoBootstrappingState],
    
    871
    +      [TorConnectState.Bootstrapped, BootstrappedState],
    
    872
    +      [TorConnectState.Error, ErrorState],
    
    873
    +      [TorConnectState.Disabled, DisabledState],
    
    874
    +    ])
    
    875
    +  ),
    
    876
    +
    
    877
    +  _makeState(state) {
    
    878
    +    const klass = this._stateCallbacks.get(state);
    
    879
    +    if (!klass) {
    
    880
    +      throw new Error(`${state} is not a valid state.`);
    
    881
    +    }
    
    882
    +    return new klass();
    
    883
    +  },
    
    884
    +
    
    885
    +  async _changeState(newState, ...args) {
    
    886
    +    if (this._stateHandler.transitioning) {
    
    887
    +      // Avoid an exception to prevent it to be propagated to the original
    
    888
    +      // begin call.
    
    889
    +      lazy.logger.warn("Already transitioning");
    
    890
    +      return;
    
    891
    +    }
    
    892
    +    const prevState = this._stateHandler;
    
    893
    +
    
    894
    +    // ensure this is a valid state transition
    
    895
    +    if (!prevState.allowedTransitions.includes(newState)) {
    
    896
    +      throw Error(
    
    897
    +        `TorConnect: Attempted invalid state transition from ${prevState.state} to ${newState}`
    
    898
    +      );
    
    899
    +    }
    
    855 900
     
    
    856
    -      // ensure this is a valid state transition
    
    857
    -      if (!TorConnectStateTransitions.get(prevState)?.includes(newState)) {
    
    858
    -        throw Error(
    
    859
    -          `TorConnect: Attempted invalid state transition from ${prevState} to ${newState}`
    
    901
    +    lazy.logger.trace(
    
    902
    +      `Try transitioning from ${prevState.state} to ${newState}`,
    
    903
    +      args
    
    904
    +    );
    
    905
    +    try {
    
    906
    +      await prevState.end(newState);
    
    907
    +    } catch (e) {
    
    908
    +      // We take for granted that the begin of this state will call us again,
    
    909
    +      // to request the transition to the error state.
    
    910
    +      if (newState !== TorConnectState.Error) {
    
    911
    +        lazy.logger.debug(
    
    912
    +          `Refusing the transition from ${prevState.state} to ${newState} because the previous state threw.`
    
    860 913
             );
    
    914
    +        return;
    
    861 915
           }
    
    916
    +    }
    
    862 917
     
    
    863
    -      lazy.logger.trace(`Try transitioning from ${prevState} to ${newState}`);
    
    864
    -
    
    865
    -      // set our new state first so that state transitions can themselves trigger
    
    866
    -      // a state transition
    
    867
    -      this._state = newState;
    
    868
    -
    
    869
    -      // call our state function and forward any args
    
    870
    -      this._callback(prevState).transition(newState, ...args);
    
    871
    -    },
    
    918
    +    // Set our new state first so that state transitions can themselves
    
    919
    +    // trigger a state transition.
    
    920
    +    this._stateHandler = this._makeState(newState);
    
    921
    +    Services.obs.notifyObservers(
    
    922
    +      { state: newState },
    
    923
    +      TorConnectTopics.StateChange
    
    924
    +    );
    
    925
    +    this._stateHandler.begin(...args);
    
    926
    +  },
    
    872 927
     
    
    873
    -    _updateBootstrapStatus(progress, status) {
    
    874
    -      this._bootstrapProgress = progress;
    
    875
    -      this._bootstrapStatus = status;
    
    928
    +  _updateBootstrapStatus(progress, status) {
    
    929
    +    this._bootstrapProgress = progress;
    
    930
    +    this._bootstrapStatus = status;
    
    876 931
     
    
    877
    -      lazy.logger.info(
    
    878
    -        `Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`
    
    879
    -      );
    
    880
    -      Services.obs.notifyObservers(
    
    881
    -        {
    
    882
    -          progress: TorConnect._bootstrapProgress,
    
    883
    -          status: TorConnect._bootstrapStatus,
    
    884
    -          hasWarnings: TorConnect._logHasWarningOrError,
    
    885
    -        },
    
    886
    -        TorConnectTopics.BootstrapProgress
    
    887
    -      );
    
    888
    -    },
    
    932
    +    lazy.logger.info(
    
    933
    +      `Bootstrapping ${this._bootstrapProgress}% complete (${this._bootstrapStatus})`
    
    934
    +    );
    
    935
    +    Services.obs.notifyObservers(
    
    936
    +      {
    
    937
    +        progress: TorConnect._bootstrapProgress,
    
    938
    +        status: TorConnect._bootstrapStatus,
    
    939
    +        hasWarnings: TorConnect._logHasWarningOrError,
    
    940
    +      },
    
    941
    +      TorConnectTopics.BootstrapProgress
    
    942
    +    );
    
    943
    +  },
    
    944
    +
    
    945
    +  // init should be called by TorStartupService
    
    946
    +  init() {
    
    947
    +    lazy.logger.debug("TorConnect.init()");
    
    948
    +    this._stateHandler.begin();
    
    949
    +
    
    950
    +    if (!this.enabled) {
    
    951
    +      // Disabled
    
    952
    +      this._changeState(TorConnectState.Disabled);
    
    953
    +    } else {
    
    954
    +      let observeTopic = addTopic => {
    
    955
    +        Services.obs.addObserver(this, addTopic);
    
    956
    +        lazy.logger.debug(`Observing topic '${addTopic}'`);
    
    957
    +      };
    
    958
    +
    
    959
    +      // Wait for TorSettings, as we will need it.
    
    960
    +      // We will wait for a TorProvider only after TorSettings is ready,
    
    961
    +      // because the TorProviderBuilder initialization might not have finished
    
    962
    +      // at this point, and TorSettings initialization is a prerequisite for
    
    963
    +      // having a provider.
    
    964
    +      // So, we prefer initializing TorConnect as soon as possible, so that
    
    965
    +      // the UI will be able to detect it is in the Initializing state and act
    
    966
    +      // consequently.
    
    967
    +      TorSettings.initializedPromise.then(() => this._settingsInitialized());
    
    968
    +
    
    969
    +      // register the Tor topics we always care about
    
    970
    +      observeTopic(TorTopics.ProcessExited);
    
    971
    +      observeTopic(TorTopics.LogHasWarnOrErr);
    
    972
    +    }
    
    973
    +  },
    
    889 974
     
    
    890
    -    // init should be called by TorStartupService
    
    891
    -    init() {
    
    892
    -      lazy.logger.debug("TorConnect.init()");
    
    893
    -      this._callback(TorConnectState.Initial).begin();
    
    975
    +  async observe(subject, topic, data) {
    
    976
    +    lazy.logger.debug(`Observed ${topic}`);
    
    894 977
     
    
    895
    -      if (!this.enabled) {
    
    896
    -        // Disabled
    
    897
    -        this._changeState(TorConnectState.Disabled);
    
    898
    -      } else {
    
    899
    -        let observeTopic = addTopic => {
    
    900
    -          Services.obs.addObserver(this, addTopic);
    
    901
    -          lazy.logger.debug(`Observing topic '${addTopic}'`);
    
    902
    -        };
    
    903
    -
    
    904
    -        // Wait for TorSettings, as we will need it.
    
    905
    -        // We will wait for a TorProvider only after TorSettings is ready,
    
    906
    -        // because the TorProviderBuilder initialization might not have finished
    
    907
    -        // at this point, and TorSettings initialization is a prerequisite for
    
    908
    -        // having a provider.
    
    909
    -        // So, we prefer initializing TorConnect as soon as possible, so that
    
    910
    -        // the UI will be able to detect it is in the Initializing state and act
    
    911
    -        // consequently.
    
    912
    -        TorSettings.initializedPromise.then(() => this._settingsInitialized());
    
    913
    -
    
    914
    -        // register the Tor topics we always care about
    
    915
    -        observeTopic(TorTopics.ProcessExited);
    
    916
    -        observeTopic(TorTopics.LogHasWarnOrErr);
    
    978
    +    switch (topic) {
    
    979
    +      case TorTopics.LogHasWarnOrErr: {
    
    980
    +        this._logHasWarningOrError = true;
    
    981
    +        break;
    
    917 982
           }
    
    918
    -    },
    
    919
    -
    
    920
    -    async observe(subject, topic, data) {
    
    921
    -      lazy.logger.debug(`Observed ${topic}`);
    
    922
    -
    
    923
    -      switch (topic) {
    
    924
    -        case TorTopics.LogHasWarnOrErr: {
    
    925
    -          this._logHasWarningOrError = true;
    
    926
    -          break;
    
    983
    +      case TorTopics.ProcessExited: {
    
    984
    +        // Treat a failure as a possibly broken configuration.
    
    985
    +        // So, prevent quickstart at the next start.
    
    986
    +        Services.prefs.setBoolPref(TorLauncherPrefs.prompt_at_startup, true);
    
    987
    +        switch (this.state) {
    
    988
    +          case TorConnectState.Bootstrapping:
    
    989
    +          case TorConnectState.AutoBootstrapping:
    
    990
    +          case TorConnectState.Bootstrapped:
    
    991
    +            // If we are in the bootstrap or auto bootstrap, we could go
    
    992
    +            // through the error phase (and eventually we might do it, if some
    
    993
    +            // transition calls fail). However, this would start the
    
    994
    +            // connection assist, so we go directly to configuring.
    
    995
    +            // FIXME: Find a better way to handle this.
    
    996
    +            this._changeState(TorConnectState.Configuring);
    
    997
    +            break;
    
    998
    +          // Other states naturally resolve in configuration.
    
    927 999
             }
    
    928
    -        case TorTopics.ProcessExited: {
    
    929
    -          // Treat a failure as a possibly broken configuration.
    
    930
    -          // So, prevent quickstart at the next start.
    
    931
    -          Services.prefs.setBoolPref(TorLauncherPrefs.prompt_at_startup, true);
    
    932
    -          switch (this._state) {
    
    933
    -            case TorConnectState.Bootstrapping:
    
    934
    -            case TorConnectState.AutoBootstrapping:
    
    935
    -            case TorConnectState.Bootstrapped:
    
    936
    -              // If we are in the bootstrap or auto bootstrap, we could go
    
    937
    -              // through the error phase (and eventually we might do it, if some
    
    938
    -              // transition calls fail). However, this would start the
    
    939
    -              // connection assist, so we go directly to configuring.
    
    940
    -              // FIXME: Find a better way to handle this.
    
    941
    -              this._changeState(TorConnectState.Configuring);
    
    942
    -              break;
    
    943
    -            // Other states naturally resolve in configuration.
    
    944
    -          }
    
    945
    -          break;
    
    946
    -        }
    
    947
    -        default:
    
    948
    -          // ignore
    
    949
    -          break;
    
    950
    -      }
    
    951
    -    },
    
    952
    -
    
    953
    -    async _settingsInitialized() {
    
    954
    -      // TODO: Handle failures here, instead of the prompt to restart the
    
    955
    -      // daemon when it exits (tor-browser#21053, tor-browser#41921).
    
    956
    -      await lazy.TorProviderBuilder.build();
    
    957
    -
    
    958
    -      // tor-browser#41907: This is only a workaround to avoid users being
    
    959
    -      // bounced back to the initial panel without any explanation.
    
    960
    -      // Longer term we should disable the clickable elements, or find a UX
    
    961
    -      // to prevent this from happening (e.g., allow buttons to be clicked,
    
    962
    -      // but show an intermediate starting state, or a message that tor is
    
    963
    -      // starting while the butons are disabled, etc...).
    
    964
    -      // See also tor-browser#41921.
    
    965
    -      if (this.state !== TorConnectState.Initial) {
    
    966
    -        lazy.logger.warn(
    
    967
    -          "The TorProvider was built after the state had already changed."
    
    968
    -        );
    
    969
    -        return;
    
    970
    -      }
    
    971
    -      lazy.logger.debug("The TorProvider is ready, changing state.");
    
    972
    -      if (this.shouldQuickStart) {
    
    973
    -        // Quickstart
    
    974
    -        this._changeState(TorConnectState.Bootstrapping);
    
    975
    -      } else {
    
    976
    -        // Configuring
    
    977
    -        this._changeState(TorConnectState.Configuring);
    
    1000
    +        break;
    
    978 1001
           }
    
    979
    -    },
    
    980
    -
    
    981
    -    /*
    
    982
    -        Various getters
    
    983
    -        */
    
    984
    -
    
    985
    -    /**
    
    986
    -     * Whether TorConnect is enabled.
    
    987
    -     *
    
    988
    -     * @type {boolean}
    
    989
    -     */
    
    990
    -    get enabled() {
    
    991
    -      // FIXME: This is called before the TorProvider is ready.
    
    992
    -      // As a matter of fact, at the moment it is equivalent to the following
    
    993
    -      // line, but this might become a problem in the future.
    
    994
    -      return TorLauncherUtil.shouldStartAndOwnTor;
    
    995
    -    },
    
    996
    -
    
    997
    -    get shouldShowTorConnect() {
    
    998
    -      // TorBrowser must control the daemon
    
    999
    -      return (
    
    1000
    -        this.enabled &&
    
    1001
    -        // if we have succesfully bootstraped, then no need to show TorConnect
    
    1002
    -        this.state !== TorConnectState.Bootstrapped
    
    1003
    -      );
    
    1004
    -    },
    
    1005
    -
    
    1006
    -    /**
    
    1007
    -     * Whether bootstrapping can currently begin.
    
    1008
    -     *
    
    1009
    -     * The value may change with TorConnectTopics.StateChanged.
    
    1010
    -     *
    
    1011
    -     * @param {boolean}
    
    1012
    -     */
    
    1013
    -    get canBeginBootstrap() {
    
    1014
    -      return TorConnectStateTransitions.get(this.state).includes(
    
    1015
    -        TorConnectState.Bootstrapping
    
    1016
    -      );
    
    1017
    -    },
    
    1018
    -
    
    1019
    -    /**
    
    1020
    -     * Whether auto-bootstrapping can currently begin.
    
    1021
    -     *
    
    1022
    -     * The value may change with TorConnectTopics.StateChanged.
    
    1023
    -     *
    
    1024
    -     * @param {boolean}
    
    1025
    -     */
    
    1026
    -    get canBeginAutoBootstrap() {
    
    1027
    -      return TorConnectStateTransitions.get(this.state).includes(
    
    1028
    -        TorConnectState.AutoBootstrapping
    
    1029
    -      );
    
    1030
    -    },
    
    1031
    -
    
    1032
    -    get shouldQuickStart() {
    
    1033
    -      // quickstart must be enabled
    
    1034
    -      return (
    
    1035
    -        TorSettings.quickstart.enabled &&
    
    1036
    -        // and the previous bootstrap attempt must have succeeded
    
    1037
    -        !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true)
    
    1002
    +      default:
    
    1003
    +        // ignore
    
    1004
    +        break;
    
    1005
    +    }
    
    1006
    +  },
    
    1007
    +
    
    1008
    +  async _settingsInitialized() {
    
    1009
    +    // TODO: Handle failures here, instead of the prompt to restart the
    
    1010
    +    // daemon when it exits (tor-browser#21053, tor-browser#41921).
    
    1011
    +    await lazy.TorProviderBuilder.build();
    
    1012
    +
    
    1013
    +    // tor-browser#41907: This is only a workaround to avoid users being
    
    1014
    +    // bounced back to the initial panel without any explanation.
    
    1015
    +    // Longer term we should disable the clickable elements, or find a UX
    
    1016
    +    // to prevent this from happening (e.g., allow buttons to be clicked,
    
    1017
    +    // but show an intermediate starting state, or a message that tor is
    
    1018
    +    // starting while the butons are disabled, etc...).
    
    1019
    +    // Notice that currently the initial state does not do anything.
    
    1020
    +    // Instead of just waiting, we could move this code in its callback.
    
    1021
    +    // See also tor-browser#41921.
    
    1022
    +    if (this.state !== TorConnectState.Initial) {
    
    1023
    +      lazy.logger.warn(
    
    1024
    +        "The TorProvider was built after the state had already changed."
    
    1038 1025
           );
    
    1039
    -    },
    
    1040
    -
    
    1041
    -    get state() {
    
    1042
    -      return this._state;
    
    1043
    -    },
    
    1044
    -
    
    1045
    -    get bootstrapProgress() {
    
    1046
    -      return this._bootstrapProgress;
    
    1047
    -    },
    
    1026
    +      return;
    
    1027
    +    }
    
    1028
    +    lazy.logger.debug("The TorProvider is ready, changing state.");
    
    1029
    +    if (this.shouldQuickStart) {
    
    1030
    +      // Quickstart
    
    1031
    +      this._changeState(TorConnectState.Bootstrapping);
    
    1032
    +    } else {
    
    1033
    +      // Configuring
    
    1034
    +      this._changeState(TorConnectState.Configuring);
    
    1035
    +    }
    
    1036
    +  },
    
    1048 1037
     
    
    1049
    -    get bootstrapStatus() {
    
    1050
    -      return this._bootstrapStatus;
    
    1051
    -    },
    
    1038
    +  /*
    
    1039
    +    Various getters
    
    1040
    +   */
    
    1052 1041
     
    
    1053
    -    get internetStatus() {
    
    1054
    -      return this._internetStatus;
    
    1055
    -    },
    
    1042
    +  /**
    
    1043
    +   * Whether TorConnect is enabled.
    
    1044
    +   *
    
    1045
    +   * @type {boolean}
    
    1046
    +   */
    
    1047
    +  get enabled() {
    
    1048
    +    // FIXME: This is called before the TorProvider is ready.
    
    1049
    +    // As a matter of fact, at the moment it is equivalent to the following
    
    1050
    +    // line, but this might become a problem in the future.
    
    1051
    +    return TorLauncherUtil.shouldStartAndOwnTor;
    
    1052
    +  },
    
    1053
    +
    
    1054
    +  get shouldShowTorConnect() {
    
    1055
    +    // TorBrowser must control the daemon
    
    1056
    +    return (
    
    1057
    +      this.enabled &&
    
    1058
    +      // if we have succesfully bootstraped, then no need to show TorConnect
    
    1059
    +      this.state !== TorConnectState.Bootstrapped
    
    1060
    +    );
    
    1061
    +  },
    
    1062
    +
    
    1063
    +  /**
    
    1064
    +   * Whether bootstrapping can currently begin.
    
    1065
    +   *
    
    1066
    +   * The value may change with TorConnectTopics.StateChanged.
    
    1067
    +   *
    
    1068
    +   * @param {boolean}
    
    1069
    +   */
    
    1070
    +  get canBeginBootstrap() {
    
    1071
    +    return this._stateHandler.allowedTransitions.includes(
    
    1072
    +      TorConnectState.Bootstrapping
    
    1073
    +    );
    
    1074
    +  },
    
    1075
    +
    
    1076
    +  /**
    
    1077
    +   * Whether auto-bootstrapping can currently begin.
    
    1078
    +   *
    
    1079
    +   * The value may change with TorConnectTopics.StateChanged.
    
    1080
    +   *
    
    1081
    +   * @param {boolean}
    
    1082
    +   */
    
    1083
    +  get canBeginAutoBootstrap() {
    
    1084
    +    return this._stateHandler.allowedTransitions.includes(
    
    1085
    +      TorConnectState.AutoBootstrapping
    
    1086
    +    );
    
    1087
    +  },
    
    1088
    +
    
    1089
    +  get shouldQuickStart() {
    
    1090
    +    // quickstart must be enabled
    
    1091
    +    return (
    
    1092
    +      TorSettings.quickstart.enabled &&
    
    1093
    +      // and the previous bootstrap attempt must have succeeded
    
    1094
    +      !Services.prefs.getBoolPref(TorLauncherPrefs.prompt_at_startup, true)
    
    1095
    +    );
    
    1096
    +  },
    
    1097
    +
    
    1098
    +  get state() {
    
    1099
    +    return this._stateHandler.state;
    
    1100
    +  },
    
    1101
    +
    
    1102
    +  get bootstrapProgress() {
    
    1103
    +    return this._bootstrapProgress;
    
    1104
    +  },
    
    1105
    +
    
    1106
    +  get bootstrapStatus() {
    
    1107
    +    return this._bootstrapStatus;
    
    1108
    +  },
    
    1109
    +
    
    1110
    +  get internetStatus() {
    
    1111
    +    return this._internetStatus;
    
    1112
    +  },
    
    1113
    +
    
    1114
    +  get countryCodes() {
    
    1115
    +    return this._countryCodes;
    
    1116
    +  },
    
    1117
    +
    
    1118
    +  get countryNames() {
    
    1119
    +    return this._countryNames;
    
    1120
    +  },
    
    1121
    +
    
    1122
    +  get detectedLocation() {
    
    1123
    +    return this._detectedLocation;
    
    1124
    +  },
    
    1125
    +
    
    1126
    +  get errorMessage() {
    
    1127
    +    return this._errorMessage;
    
    1128
    +  },
    
    1129
    +
    
    1130
    +  get errorDetails() {
    
    1131
    +    return this._errorDetails;
    
    1132
    +  },
    
    1133
    +
    
    1134
    +  get logHasWarningOrError() {
    
    1135
    +    return this._logHasWarningOrError;
    
    1136
    +  },
    
    1137
    +
    
    1138
    +  /**
    
    1139
    +   * Whether we have ever entered the Error state.
    
    1140
    +   *
    
    1141
    +   * @type {boolean}
    
    1142
    +   */
    
    1143
    +  get hasEverFailed() {
    
    1144
    +    return ErrorState.hasEverHappened;
    
    1145
    +  },
    
    1146
    +
    
    1147
    +  /**
    
    1148
    +   * Whether the Bootstrapping process has ever failed, not including when it
    
    1149
    +   * failed due to not being connected to the internet.
    
    1150
    +   *
    
    1151
    +   * This does not include a failure in AutoBootstrapping.
    
    1152
    +   *
    
    1153
    +   * @type {boolean}
    
    1154
    +   */
    
    1155
    +  get potentiallyBlocked() {
    
    1156
    +    return this._hasBootstrapEverFailed;
    
    1157
    +  },
    
    1158
    +
    
    1159
    +  get uiState() {
    
    1160
    +    return this._uiState;
    
    1161
    +  },
    
    1162
    +  set uiState(newState) {
    
    1163
    +    this._uiState = newState;
    
    1164
    +  },
    
    1165
    +
    
    1166
    +  /*
    
    1167
    +    These functions allow external consumers to tell TorConnect to transition states
    
    1168
    +   */
    
    1169
    +
    
    1170
    +  beginBootstrap() {
    
    1171
    +    lazy.logger.debug("TorConnect.beginBootstrap()");
    
    1172
    +    this._changeState(TorConnectState.Bootstrapping);
    
    1173
    +  },
    
    1174
    +
    
    1175
    +  cancelBootstrap() {
    
    1176
    +    lazy.logger.debug("TorConnect.cancelBootstrap()");
    
    1177
    +    if (
    
    1178
    +      this.state !== TorConnectState.AutoBootstrapping &&
    
    1179
    +      this.state !== TorConnectState.Bootstrapping
    
    1180
    +    ) {
    
    1181
    +      lazy.logger.warn(
    
    1182
    +        `Cannot cancel bootstrapping in the ${this.state} state`
    
    1183
    +      );
    
    1184
    +      return;
    
    1185
    +    }
    
    1186
    +    this._changeState(TorConnectState.Configuring);
    
    1187
    +  },
    
    1188
    +
    
    1189
    +  beginAutoBootstrap(countryCode) {
    
    1190
    +    lazy.logger.debug("TorConnect.beginAutoBootstrap()");
    
    1191
    +    this._changeState(TorConnectState.AutoBootstrapping, countryCode);
    
    1192
    +  },
    
    1193
    +
    
    1194
    +  /*
    
    1195
    +    Further external commands and helper methods
    
    1196
    +   */
    
    1197
    +  openTorPreferences() {
    
    1198
    +    if (TorLauncherUtil.isAndroid) {
    
    1199
    +      lazy.EventDispatcher.instance.sendRequest({
    
    1200
    +        type: "GeckoView:Tor:OpenSettings",
    
    1201
    +      });
    
    1202
    +      return;
    
    1203
    +    }
    
    1204
    +    const win = lazy.BrowserWindowTracker.getTopWindow();
    
    1205
    +    win.switchToTabHavingURI("about:preferences#connection", true);
    
    1206
    +  },
    
    1207
    +
    
    1208
    +  /**
    
    1209
    +   * Open the "about:torconnect" tab.
    
    1210
    +   *
    
    1211
    +   * Bootstrapping or AutoBootstrapping can also be automatically triggered at
    
    1212
    +   * the same time, if the current state allows for it.
    
    1213
    +   *
    
    1214
    +   * Bootstrapping will not be triggered if the connection is
    
    1215
    +   * potentially blocked.
    
    1216
    +   *
    
    1217
    +   * @param {object} [options] - extra options.
    
    1218
    +   * @property {boolean} [options.beginBootstrap=false] - Whether to try and
    
    1219
    +   *   begin Bootstrapping.
    
    1220
    +   * @property {string} [options.beginAutoBootstrap] - The location to use to
    
    1221
    +   *   begin AutoBootstrapping, if possible.
    
    1222
    +   */
    
    1223
    +  openTorConnect(options) {
    
    1224
    +    const win = lazy.BrowserWindowTracker.getTopWindow();
    
    1225
    +    win.switchToTabHavingURI("about:torconnect", true, {
    
    1226
    +      ignoreQueryString: true,
    
    1227
    +    });
    
    1228
    +    if (
    
    1229
    +      options?.beginBootstrap &&
    
    1230
    +      this.canBeginBootstrap &&
    
    1231
    +      !this.potentiallyBlocked
    
    1232
    +    ) {
    
    1233
    +      this.beginBootstrap();
    
    1234
    +    }
    
    1235
    +    // options.beginAutoBootstrap can be an empty string.
    
    1236
    +    if (
    
    1237
    +      options?.beginAutoBootstrap !== undefined &&
    
    1238
    +      this.canBeginAutoBootstrap
    
    1239
    +    ) {
    
    1240
    +      this.beginAutoBootstrap(options.beginAutoBootstrap);
    
    1241
    +    }
    
    1242
    +  },
    
    1056 1243
     
    
    1057
    -    get countryCodes() {
    
    1058
    -      return this._countryCodes;
    
    1059
    -    },
    
    1060
    -
    
    1061
    -    get countryNames() {
    
    1062
    -      return this._countryNames;
    
    1063
    -    },
    
    1064
    -
    
    1065
    -    get detectedLocation() {
    
    1066
    -      return this._detectedLocation;
    
    1067
    -    },
    
    1068
    -
    
    1069
    -    get errorMessage() {
    
    1070
    -      return this._errorMessage;
    
    1071
    -    },
    
    1072
    -
    
    1073
    -    get errorDetails() {
    
    1074
    -      return this._errorDetails;
    
    1075
    -    },
    
    1076
    -
    
    1077
    -    get logHasWarningOrError() {
    
    1078
    -      return this._logHasWarningOrError;
    
    1079
    -    },
    
    1080
    -
    
    1081
    -    /**
    
    1082
    -     * Whether we have ever entered the Error state.
    
    1083
    -     *
    
    1084
    -     * @type {boolean}
    
    1085
    -     */
    
    1086
    -    get hasEverFailed() {
    
    1087
    -      return this._hasEverFailed;
    
    1088
    -    },
    
    1089
    -
    
    1090
    -    /**
    
    1091
    -     * Whether the Bootstrapping process has ever failed, not including when it
    
    1092
    -     * failed due to not being connected to the internet.
    
    1093
    -     *
    
    1094
    -     * This does not include a failure in AutoBootstrapping.
    
    1095
    -     *
    
    1096
    -     * @type {boolean}
    
    1097
    -     */
    
    1098
    -    get potentiallyBlocked() {
    
    1099
    -      return this._hasBootstrapEverFailed;
    
    1100
    -    },
    
    1101
    -
    
    1102
    -    get uiState() {
    
    1103
    -      return this._uiState;
    
    1104
    -    },
    
    1105
    -    set uiState(newState) {
    
    1106
    -      this._uiState = newState;
    
    1107
    -    },
    
    1108
    -
    
    1109
    -    /*
    
    1110
    -        These functions allow external consumers to tell TorConnect to transition states
    
    1111
    -        */
    
    1112
    -
    
    1113
    -    beginBootstrap() {
    
    1114
    -      lazy.logger.debug("TorConnect.beginBootstrap()");
    
    1115
    -      this._changeState(TorConnectState.Bootstrapping);
    
    1116
    -    },
    
    1244
    +  viewTorLogs() {
    
    1245
    +    const win = lazy.BrowserWindowTracker.getTopWindow();
    
    1246
    +    win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
    
    1247
    +  },
    
    1117 1248
     
    
    1118
    -    cancelBootstrap() {
    
    1119
    -      lazy.logger.debug("TorConnect.cancelBootstrap()");
    
    1120
    -      this._changeState(TorConnectState.Configuring);
    
    1121
    -    },
    
    1122
    -
    
    1123
    -    beginAutoBootstrap(countryCode) {
    
    1124
    -      lazy.logger.debug("TorConnect.beginAutoBootstrap()");
    
    1125
    -      this._changeState(TorConnectState.AutoBootstrapping, countryCode);
    
    1126
    -    },
    
    1127
    -
    
    1128
    -    /*
    
    1129
    -        Further external commands and helper methods
    
    1130
    -        */
    
    1131
    -    openTorPreferences() {
    
    1132
    -      if (TorLauncherUtil.isAndroid) {
    
    1133
    -        lazy.EventDispatcher.instance.sendRequest({
    
    1134
    -          type: "GeckoView:Tor:OpenSettings",
    
    1135
    -        });
    
    1136
    -        return;
    
    1137
    -      }
    
    1138
    -      const win = lazy.BrowserWindowTracker.getTopWindow();
    
    1139
    -      win.switchToTabHavingURI("about:preferences#connection", true);
    
    1140
    -    },
    
    1141
    -
    
    1142
    -    /**
    
    1143
    -     * Open the "about:torconnect" tab.
    
    1144
    -     *
    
    1145
    -     * Bootstrapping or AutoBootstrapping can also be automatically triggered at
    
    1146
    -     * the same time, if the current state allows for it.
    
    1147
    -     *
    
    1148
    -     * Bootstrapping will not be triggered if the connection is
    
    1149
    -     * potentially blocked.
    
    1150
    -     *
    
    1151
    -     * @param {object} [options] - extra options.
    
    1152
    -     * @property {boolean} [options.beginBootstrap=false] - Whether to try and
    
    1153
    -     *   begin Bootstrapping.
    
    1154
    -     * @property {string} [options.beginAutoBootstrap] - The location to use to
    
    1155
    -     *   begin AutoBootstrapping, if possible.
    
    1156
    -     */
    
    1157
    -    openTorConnect(options) {
    
    1158
    -      const win = lazy.BrowserWindowTracker.getTopWindow();
    
    1159
    -      win.switchToTabHavingURI("about:torconnect", true, {
    
    1160
    -        ignoreQueryString: true,
    
    1161
    -      });
    
    1162
    -      if (
    
    1163
    -        options?.beginBootstrap &&
    
    1164
    -        this.canBeginBootstrap &&
    
    1165
    -        !this.potentiallyBlocked
    
    1166
    -      ) {
    
    1167
    -        this.beginBootstrap();
    
    1168
    -      }
    
    1169
    -      // options.beginAutoBootstrap can be an empty string.
    
    1170
    -      if (
    
    1171
    -        options?.beginAutoBootstrap !== undefined &&
    
    1172
    -        this.canBeginAutoBootstrap
    
    1173
    -      ) {
    
    1174
    -        this.beginAutoBootstrap(options.beginAutoBootstrap);
    
    1175
    -      }
    
    1176
    -    },
    
    1177
    -
    
    1178
    -    viewTorLogs() {
    
    1179
    -      const win = lazy.BrowserWindowTracker.getTopWindow();
    
    1180
    -      win.switchToTabHavingURI("about:preferences#connection-viewlogs", true);
    
    1181
    -    },
    
    1182
    -
    
    1183
    -    async getCountryCodes() {
    
    1184
    -      // Difference with the getter: this is to be called by TorConnectParent, and downloads
    
    1185
    -      // the country codes if they are not already in cache.
    
    1186
    -      if (this._countryCodes.length) {
    
    1187
    -        return this._countryCodes;
    
    1188
    -      }
    
    1189
    -      const mrpc = new lazy.MoatRPC();
    
    1190
    -      try {
    
    1191
    -        await mrpc.init();
    
    1192
    -        this._countryCodes = await mrpc.circumvention_countries();
    
    1193
    -      } catch (err) {
    
    1194
    -        lazy.logger.error(
    
    1195
    -          "An error occurred while fetching country codes",
    
    1196
    -          err
    
    1197
    -        );
    
    1198
    -      } finally {
    
    1199
    -        mrpc.uninit();
    
    1200
    -      }
    
    1249
    +  async getCountryCodes() {
    
    1250
    +    // Difference with the getter: this is to be called by TorConnectParent, and
    
    1251
    +    // downloads the country codes if they are not already in cache.
    
    1252
    +    if (this._countryCodes.length) {
    
    1201 1253
           return this._countryCodes;
    
    1202
    -    },
    
    1203
    -
    
    1204
    -    getRedirectURL(url) {
    
    1205
    -      return `about:torconnect?redirect=${encodeURIComponent(url)}`;
    
    1206
    -    },
    
    1207
    -
    
    1208
    -    /**
    
    1209
    -     * Convert the given object into a list of valid URIs.
    
    1210
    -     *
    
    1211
    -     * The object is either from the user's homepage preference (which may
    
    1212
    -     * contain multiple domains separated by "|") or uris passed to the browser
    
    1213
    -     * via command-line.
    
    1214
    -     *
    
    1215
    -     * @param {string|string[]} uriVariant - The string to extract uris from.
    
    1216
    -     *
    
    1217
    -     * @return {string[]} - The array of uris found.
    
    1218
    -     */
    
    1219
    -    fixupURIs(uriVariant) {
    
    1220
    -      let uriArray;
    
    1221
    -      if (typeof uriVariant === "string") {
    
    1222
    -        uriArray = uriVariant.split("|");
    
    1223
    -      } else if (
    
    1224
    -        Array.isArray(uriVariant) &&
    
    1225
    -        uriVariant.every(entry => typeof entry === "string")
    
    1226
    -      ) {
    
    1227
    -        uriArray = uriVariant;
    
    1228
    -      } else {
    
    1229
    -        // about:tor as safe fallback
    
    1230
    -        lazy.logger.error(
    
    1231
    -          `Received unknown variant '${JSON.stringify(uriVariant)}'`
    
    1232
    -        );
    
    1233
    -        uriArray = ["about:tor"];
    
    1234
    -      }
    
    1235
    -
    
    1236
    -      // Attempt to convert user-supplied string to a uri, fallback to
    
    1237
    -      // about:tor if cannot convert to valid uri object
    
    1238
    -      return uriArray.map(
    
    1239
    -        uriString =>
    
    1240
    -          Services.uriFixup.getFixupURIInfo(
    
    1241
    -            uriString,
    
    1242
    -            Ci.nsIURIFixup.FIXUP_FLAG_NONE
    
    1243
    -          ).preferredURI?.spec ?? "about:tor"
    
    1254
    +    }
    
    1255
    +    const mrpc = new lazy.MoatRPC();
    
    1256
    +    try {
    
    1257
    +      await mrpc.init();
    
    1258
    +      this._countryCodes = await mrpc.circumvention_countries();
    
    1259
    +    } catch (err) {
    
    1260
    +      lazy.logger.error("An error occurred while fetching country codes", err);
    
    1261
    +    } finally {
    
    1262
    +      mrpc.uninit();
    
    1263
    +    }
    
    1264
    +    return this._countryCodes;
    
    1265
    +  },
    
    1266
    +
    
    1267
    +  getRedirectURL(url) {
    
    1268
    +    return `about:torconnect?redirect=${encodeURIComponent(url)}`;
    
    1269
    +  },
    
    1270
    +
    
    1271
    +  /**
    
    1272
    +   * Convert the given object into a list of valid URIs.
    
    1273
    +   *
    
    1274
    +   * The object is either from the user's homepage preference (which may
    
    1275
    +   * contain multiple domains separated by "|") or uris passed to the browser
    
    1276
    +   * via command-line.
    
    1277
    +   *
    
    1278
    +   * @param {string|string[]} uriVariant - The string to extract uris from.
    
    1279
    +   *
    
    1280
    +   * @return {string[]} - The array of uris found.
    
    1281
    +   */
    
    1282
    +  fixupURIs(uriVariant) {
    
    1283
    +    let uriArray;
    
    1284
    +    if (typeof uriVariant === "string") {
    
    1285
    +      uriArray = uriVariant.split("|");
    
    1286
    +    } else if (
    
    1287
    +      Array.isArray(uriVariant) &&
    
    1288
    +      uriVariant.every(entry => typeof entry === "string")
    
    1289
    +    ) {
    
    1290
    +      uriArray = uriVariant;
    
    1291
    +    } else {
    
    1292
    +      // about:tor as safe fallback
    
    1293
    +      lazy.logger.error(
    
    1294
    +        `Received unknown variant '${JSON.stringify(uriVariant)}'`
    
    1244 1295
           );
    
    1245
    -    },
    
    1246
    -
    
    1247
    -    // called from browser.js on browser startup, passed in either the user's homepage(s)
    
    1248
    -    // or uris passed via command-line; we want to replace them with about:torconnect uris
    
    1249
    -    // which redirect after bootstrapping
    
    1250
    -    getURIsToLoad(uriVariant) {
    
    1251
    -      const uris = this.fixupURIs(uriVariant);
    
    1252
    -      lazy.logger.debug(`Will load after bootstrap => [${uris.join(", ")}]`);
    
    1253
    -      return uris.map(uri => this.getRedirectURL(uri));
    
    1254
    -    },
    
    1255
    -  };
    
    1256
    -  return retval;
    
    1257
    -})(); /* TorConnect */
    1296
    +      uriArray = ["about:tor"];
    
    1297
    +    }
    
    1298
    +
    
    1299
    +    // Attempt to convert user-supplied string to a uri, fallback to
    
    1300
    +    // about:tor if cannot convert to valid uri object
    
    1301
    +    return uriArray.map(
    
    1302
    +      uriString =>
    
    1303
    +        Services.uriFixup.getFixupURIInfo(
    
    1304
    +          uriString,
    
    1305
    +          Ci.nsIURIFixup.FIXUP_FLAG_NONE
    
    1306
    +        ).preferredURI?.spec ?? "about:tor"
    
    1307
    +    );
    
    1308
    +  },
    
    1309
    +
    
    1310
    +  // called from browser.js on browser startup, passed in either the user's homepage(s)
    
    1311
    +  // or uris passed via command-line; we want to replace them with about:torconnect uris
    
    1312
    +  // which redirect after bootstrapping
    
    1313
    +  getURIsToLoad(uriVariant) {
    
    1314
    +    const uris = this.fixupURIs(uriVariant);
    
    1315
    +    lazy.logger.debug(`Will load after bootstrap => [${uris.join(", ")}]`);
    
    1316
    +    return uris.map(uri => this.getRedirectURL(uri));
    
    1317
    +  },
    
    1318
    +};

  • _______________________________________________
    tor-commits mailing list
    tor-commits@xxxxxxxxxxxxxxxxxxxx
    https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits