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

Commits:

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
    +};