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

Commits:

4 changed files:

Changes:

  • browser/components/torpreferences/content/connectionPane.js
    ... ... @@ -11,7 +11,7 @@ const { setTimeout, clearTimeout } = ChromeUtils.import(
    11 11
       "resource://gre/modules/Timer.jsm"
    
    12 12
     );
    
    13 13
     
    
    14
    -const { TorSettings, TorSettingsTopics, TorSettingsData, TorBridgeSource } =
    
    14
    +const { TorSettings, TorSettingsTopics, TorBridgeSource } =
    
    15 15
       ChromeUtils.importESModule("resource://gre/modules/TorSettings.sys.mjs");
    
    16 16
     
    
    17 17
     const { TorParsers } = ChromeUtils.importESModule(
    
    ... ... @@ -285,7 +285,7 @@ const gConnectionPane = (function () {
    285 285
             TorSettings.saveToPrefs().applySettings();
    
    286 286
           });
    
    287 287
           this._enableQuickstartCheckbox.checked = TorSettings.quickstart.enabled;
    
    288
    -      Services.obs.addObserver(this, TorSettingsTopics.SettingChanged);
    
    288
    +      Services.obs.addObserver(this, TorSettingsTopics.SettingsChanged);
    
    289 289
     
    
    290 290
           // Bridge setup
    
    291 291
           prefpane.querySelector(selectors.bridges.header).innerText =
    
    ... ... @@ -885,7 +885,7 @@ const gConnectionPane = (function () {
    885 885
     
    
    886 886
         uninit() {
    
    887 887
           // unregister our observer topics
    
    888
    -      Services.obs.removeObserver(this, TorSettingsTopics.SettingChanged);
    
    888
    +      Services.obs.removeObserver(this, TorSettingsTopics.SettingsChanged);
    
    889 889
           Services.obs.removeObserver(this, TorConnectTopics.StateChange);
    
    890 890
           Services.obs.removeObserver(this, TorProviderTopics.BridgeChanged);
    
    891 891
           Services.obs.removeObserver(this, "intl:app-locales-changed");
    
    ... ... @@ -903,13 +903,10 @@ const gConnectionPane = (function () {
    903 903
         observe(subject, topic, data) {
    
    904 904
           switch (topic) {
    
    905 905
             // triggered when a TorSettings param has changed
    
    906
    -        case TorSettingsTopics.SettingChanged: {
    
    907
    -          const obj = subject?.wrappedJSObject;
    
    908
    -          switch (data) {
    
    909
    -            case TorSettingsData.QuickStartEnabled: {
    
    910
    -              this._enableQuickstartCheckbox.checked = obj.value;
    
    911
    -              break;
    
    912
    -            }
    
    906
    +        case TorSettingsTopics.SettingsChanged: {
    
    907
    +          if (subject.wrappedJSObject.changes.includes("quickstart.enabled")) {
    
    908
    +            this._enableQuickstartCheckbox.checked =
    
    909
    +              TorSettings.quickstart.enabled;
    
    913 910
               }
    
    914 911
               break;
    
    915 912
             }
    

  • browser/components/torpreferences/content/connectionSettingsDialog.mjs
    ... ... @@ -339,6 +339,8 @@ export class ConnectionSettingsDialog {
    339 339
             TorSettings.proxy.type = type;
    
    340 340
             TorSettings.proxy.address = address;
    
    341 341
             TorSettings.proxy.port = port;
    
    342
    +        TorSettings.proxy.username = "";
    
    343
    +        TorSettings.proxy.password = "";
    
    342 344
             break;
    
    343 345
           case TorProxyType.Socks5:
    
    344 346
             TorSettings.proxy.enabled = true;
    

  • toolkit/components/torconnect/TorConnectParent.sys.mjs
    ... ... @@ -12,7 +12,6 @@ import {
    12 12
     import {
    
    13 13
       TorSettings,
    
    14 14
       TorSettingsTopics,
    
    15
    -  TorSettingsData,
    
    16 15
     } from "resource://gre/modules/TorSettings.sys.mjs";
    
    17 16
     
    
    18 17
     const BroadcastTopic = "about-torconnect:broadcast";
    
    ... ... @@ -115,9 +114,11 @@ export class TorConnectParent extends JSWindowActorParent {
    115 114
                 }
    
    116 115
                 break;
    
    117 116
               }
    
    118
    -          case TorSettingsTopics.SettingChanged: {
    
    119
    -            if (aData === TorSettingsData.QuickStartEnabled) {
    
    120
    -              self.state.QuickStartEnabled = obj.value;
    
    117
    +          case TorSettingsTopics.SettingsChanged: {
    
    118
    +            if (
    
    119
    +              aSubject.wrappedJSObject.changes.includes("quickstart.enabled")
    
    120
    +            ) {
    
    121
    +              self.state.QuickStartEnabled = TorSettings.quickstart.enabled;
    
    121 122
                 } else {
    
    122 123
                   // this isn't a setting torconnect cares about
    
    123 124
                   return;
    
    ... ... @@ -141,7 +142,7 @@ export class TorConnectParent extends JSWindowActorParent {
    141 142
         Services.obs.addObserver(this.torConnectObserver, TorSettingsTopics.Ready);
    
    142 143
         Services.obs.addObserver(
    
    143 144
           this.torConnectObserver,
    
    144
    -      TorSettingsTopics.SettingChanged
    
    145
    +      TorSettingsTopics.SettingsChanged
    
    145 146
         );
    
    146 147
     
    
    147 148
         this.userActionObserver = {
    
    ... ... @@ -168,7 +169,7 @@ export class TorConnectParent extends JSWindowActorParent {
    168 169
         );
    
    169 170
         Services.obs.removeObserver(
    
    170 171
           this.torConnectObserver,
    
    171
    -      TorSettingsTopics.SettingChanged
    
    172
    +      TorSettingsTopics.SettingsChanged
    
    172 173
         );
    
    173 174
         Services.obs.removeObserver(this.userActionObserver, BroadcastTopic);
    
    174 175
       }
    

  • toolkit/modules/TorSettings.sys.mjs
    ... ... @@ -10,15 +10,21 @@ ChromeUtils.defineESModuleGetters(lazy, {
    10 10
       TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    11 11
     });
    
    12 12
     
    
    13
    +ChromeUtils.defineLazyGetter(lazy, "logger", () => {
    
    14
    +  let { ConsoleAPI } = ChromeUtils.importESModule(
    
    15
    +    "resource://gre/modules/Console.sys.mjs"
    
    16
    +  );
    
    17
    +  return new ConsoleAPI({
    
    18
    +    maxLogLevel: "warn",
    
    19
    +    maxLogLevelPref: "browser.torsettings.log_level",
    
    20
    +    prefix: "TorSettings",
    
    21
    +  });
    
    22
    +});
    
    23
    +
    
    13 24
     /* TorSettings observer topics */
    
    14 25
     export const TorSettingsTopics = Object.freeze({
    
    15 26
       Ready: "torsettings:ready",
    
    16
    -  SettingChanged: "torsettings:setting-changed",
    
    17
    -});
    
    18
    -
    
    19
    -/* TorSettings observer data (for SettingChanged topic) */
    
    20
    -export const TorSettingsData = Object.freeze({
    
    21
    -  QuickStartEnabled: "torsettings:quickstart_enabled",
    
    27
    +  SettingsChanged: "torsettings:settings-changed",
    
    22 28
     });
    
    23 29
     
    
    24 30
     /* Prefs used to store settings in TorBrowser prefs */
    
    ... ... @@ -32,7 +38,7 @@ const TorSettingsPrefs = Object.freeze({
    32 38
       bridges: {
    
    33 39
         /* bool:  does tor use bridges */
    
    34 40
         enabled: "torbrowser.settings.bridges.enabled",
    
    35
    -    /* int: -1=invalid|0=builtin|1=bridge_db|2=user_provided */
    
    41
    +    /* int: See TorBridgeSource */
    
    36 42
         source: "torbrowser.settings.bridges.source",
    
    37 43
         /* string: obfs4|meek_azure|snowflake|etc */
    
    38 44
         builtin_type: "torbrowser.settings.bridges.builtin_type",
    
    ... ... @@ -42,7 +48,7 @@ const TorSettingsPrefs = Object.freeze({
    42 48
       proxy: {
    
    43 49
         /* bool: does tor use a proxy */
    
    44 50
         enabled: "torbrowser.settings.proxy.enabled",
    
    45
    -    /* -1=invalid|0=socks4,1=socks5,2=https */
    
    51
    +    /* See TorProxyType */
    
    46 52
         type: "torbrowser.settings.proxy.type",
    
    47 53
         /* string: proxy server address */
    
    48 54
         address: "torbrowser.settings.proxy.address",
    
    ... ... @@ -140,23 +146,6 @@ export const TorBuiltinBridgeTypes = Object.freeze(
    140 146
     
    
    141 147
     /* Parsing Methods */
    
    142 148
     
    
    143
    -// expects a string representation of an integer from 1 to 65535
    
    144
    -const parsePort = function (aPort) {
    
    145
    -  // ensure port string is a valid positive integer
    
    146
    -  const validIntRegex = /^[0-9]+$/;
    
    147
    -  if (!validIntRegex.test(aPort)) {
    
    148
    -    return 0;
    
    149
    -  }
    
    150
    -
    
    151
    -  // ensure port value is on valid range
    
    152
    -  const port = Number.parseInt(aPort);
    
    153
    -  if (port < 1 || port > 65535) {
    
    154
    -    return 0;
    
    155
    -  }
    
    156
    -
    
    157
    -  return port;
    
    158
    -};
    
    159
    -
    
    160 149
     // expects a '\n' or '\r\n' delimited bridge string, which we split and trim
    
    161 150
     // each bridge string can also optionally have 'bridge' at the beginning ie:
    
    162 151
     // bridge $(type) $(address):$(port) $(certificate)
    
    ... ... @@ -176,17 +165,6 @@ const parseBridgeStrings = function (aBridgeStrings) {
    176 165
         .filter(bridgeString => bridgeString != "");
    
    177 166
     };
    
    178 167
     
    
    179
    -// expecting a ',' delimited list of ints with possible white space between
    
    180
    -// returns an array of ints
    
    181
    -const parsePortList = function (aPortListString) {
    
    182
    -  const splitStrings = aPortListString.split(",");
    
    183
    -  // parse and remove duplicates
    
    184
    -  const portSet = new Set(splitStrings.map(val => parsePort(val.trim())));
    
    185
    -  // parsePort returns 0 for failed parses, so remove 0 from list
    
    186
    -  portSet.delete(0);
    
    187
    -  return Array.from(portSet);
    
    188
    -};
    
    189
    -
    
    190 168
     const getBuiltinBridgeStrings = function (builtinType) {
    
    191 169
       if (!builtinType) {
    
    192 170
         return [];
    
    ... ... @@ -235,558 +213,679 @@ const arrayShuffle = function (array) {
    235 213
       }
    
    236 214
     };
    
    237 215
     
    
    238
    -const arrayCopy = function (array) {
    
    239
    -  return [].concat(array);
    
    240
    -};
    
    241
    -
    
    242 216
     /* TorSettings module */
    
    243 217
     
    
    244
    -export const TorSettings = (() => {
    
    245
    -  const self = {
    
    246
    -    _settings: null,
    
    218
    +export const TorSettings = {
    
    219
    +  /**
    
    220
    +   * The underlying settings values.
    
    221
    +   *
    
    222
    +   * @type {object}
    
    223
    +   */
    
    224
    +  _settings: {
    
    225
    +    quickstart: {
    
    226
    +      enabled: false,
    
    227
    +    },
    
    228
    +    bridges: {
    
    229
    +      enabled: false,
    
    230
    +      source: TorBridgeSource.Invalid,
    
    231
    +      builtin_type: "",
    
    232
    +      bridge_strings: [],
    
    233
    +    },
    
    234
    +    proxy: {
    
    235
    +      enabled: false,
    
    236
    +      type: TorProxyType.Invalid,
    
    237
    +      address: "",
    
    238
    +      port: 0,
    
    239
    +      username: "",
    
    240
    +      password: "",
    
    241
    +    },
    
    242
    +    firewall: {
    
    243
    +      enabled: false,
    
    244
    +      allowed_ports: [],
    
    245
    +    },
    
    246
    +  },
    
    247
    +
    
    248
    +  /**
    
    249
    +   * The current number of freezes applied to the notifications.
    
    250
    +   *
    
    251
    +   * @type {integer}
    
    252
    +   */
    
    253
    +  _freezeNotificationsCount: 0,
    
    254
    +  /**
    
    255
    +   * The queue for settings that have changed. To be broadcast in the
    
    256
    +   * notification when not frozen.
    
    257
    +   *
    
    258
    +   * @type {Set<string>}
    
    259
    +   */
    
    260
    +  _notificationQueue: new Set(),
    
    261
    +  /**
    
    262
    +   * Send a notification if we have any queued and we are not frozen.
    
    263
    +   */
    
    264
    +  _tryNotification() {
    
    265
    +    if (this._freezeNotificationsCount || !this._notificationQueue.size) {
    
    266
    +      return;
    
    267
    +    }
    
    268
    +    Services.obs.notifyObservers(
    
    269
    +      { changes: [...this._notificationQueue] },
    
    270
    +      TorSettingsTopics.SettingsChanged
    
    271
    +    );
    
    272
    +    this._notificationQueue.clear();
    
    273
    +  },
    
    274
    +  /**
    
    275
    +   * Pause notifications for changes in setting values. This is useful if you
    
    276
    +   * need to make batch changes to settings.
    
    277
    +   *
    
    278
    +   * This should always be paired with a call to thawNotifications once
    
    279
    +   * notifications should be released. Usually you should wrap whatever
    
    280
    +   * changes you make with a `try` block and call thawNotifications in the
    
    281
    +   * `finally` block.
    
    282
    +   */
    
    283
    +  freezeNotifications() {
    
    284
    +    this._freezeNotificationsCount++;
    
    285
    +  },
    
    286
    +  /**
    
    287
    +   * Release the hold on notifications so they may be sent out.
    
    288
    +   *
    
    289
    +   * Note, if some other method has also frozen the notifications, this will
    
    290
    +   * only release them once it has also called this method.
    
    291
    +   */
    
    292
    +  thawNotifications() {
    
    293
    +    this._freezeNotificationsCount--;
    
    294
    +    this._tryNotification();
    
    295
    +  },
    
    296
    +  /**
    
    297
    +   * @typedef {object} TorSettingProperty
    
    298
    +   *
    
    299
    +   * @property {function} [getter] - A getter for the property. If this is
    
    300
    +   *   given, the property cannot be set.
    
    301
    +   * @property {function} [transform] - Called in the setter for the property,
    
    302
    +   *   with the new value given. Should transform the given value into the
    
    303
    +   *   right type.
    
    304
    +   * @property {function} [equal] - Test whether two values for the property
    
    305
    +   *   are considered equal. Otherwise uses `===`.
    
    306
    +   * @property {function} [callback] - Called whenever the property value
    
    307
    +   *   changes, with the new value given. Should be used to trigger any other
    
    308
    +   *   required changes for the new value.
    
    309
    +   * @property {function} [copy] - Called whenever the property is read, with
    
    310
    +   *   the stored value given. Should return a copy of the value. Otherwise
    
    311
    +   *   returns the stored value.
    
    312
    +   */
    
    313
    +  /**
    
    314
    +   * Add properties to the TorSettings instance, to be read or set.
    
    315
    +   *
    
    316
    +   * @param {string} groupname - The name of the setting group. The given
    
    317
    +   *   settings will be accessible from the TorSettings property of the same
    
    318
    +   *   name.
    
    319
    +   * @param {object<string, TorSettingProperty>} propParams - An object that
    
    320
    +   *   defines the settings to add to this group. The object property names
    
    321
    +   *   will be mapped to properties of TorSettings under the given groupname
    
    322
    +   *   property. Details about the setting should be described in the
    
    323
    +   *   TorSettingProperty property value.
    
    324
    +   */
    
    325
    +  _addProperties(groupname, propParams) {
    
    326
    +    // Create a new object to hold all these settings.
    
    327
    +    const group = {};
    
    328
    +    for (const name in propParams) {
    
    329
    +      const { getter, transform, callback, copy, equal } = propParams[name];
    
    330
    +      Object.defineProperty(group, name, {
    
    331
    +        get: getter
    
    332
    +          ? getter
    
    333
    +          : () => {
    
    334
    +              let val = this._settings[groupname][name];
    
    335
    +              if (copy) {
    
    336
    +                val = copy(val);
    
    337
    +              }
    
    338
    +              // Assume string or number value.
    
    339
    +              return val;
    
    340
    +            },
    
    341
    +        set: getter
    
    342
    +          ? undefined
    
    343
    +          : val => {
    
    344
    +              const prevVal = this._settings[groupname][name];
    
    345
    +              this.freezeNotifications();
    
    346
    +              try {
    
    347
    +                if (transform) {
    
    348
    +                  val = transform(val);
    
    349
    +                }
    
    350
    +                const isEqual = equal ? equal(val, prevVal) : val === prevVal;
    
    351
    +                if (!isEqual) {
    
    352
    +                  if (callback) {
    
    353
    +                    callback(val);
    
    354
    +                  }
    
    355
    +                  this._settings[groupname][name] = val;
    
    356
    +                  this._notificationQueue.add(`${groupname}.${name}`);
    
    357
    +                }
    
    358
    +              } finally {
    
    359
    +                this.thawNotifications();
    
    360
    +              }
    
    361
    +            },
    
    362
    +      });
    
    363
    +    }
    
    364
    +    // The group object itself should not be writable.
    
    365
    +    Object.preventExtensions(group);
    
    366
    +    Object.defineProperty(this, groupname, {
    
    367
    +      writable: false,
    
    368
    +      value: group,
    
    369
    +    });
    
    370
    +  },
    
    247 371
     
    
    248
    -    // tor daemon related settings
    
    249
    -    defaultSettings() {
    
    250
    -      const settings = {
    
    251
    -        quickstart: {
    
    252
    -          enabled: false,
    
    372
    +  /**
    
    373
    +   * Regular expression for a decimal non-negative integer.
    
    374
    +   *
    
    375
    +   * @type {RegExp}
    
    376
    +   */
    
    377
    +  _portRegex: /^[0-9]+$/,
    
    378
    +  /**
    
    379
    +   * Parse a string as a port number.
    
    380
    +   *
    
    381
    +   * @param {string|integer} val - The value to parse.
    
    382
    +   * @param {boolean} trim - Whether a string value can be stripped of
    
    383
    +   *   whitespace before parsing.
    
    384
    +   *
    
    385
    +   * @return {integer?} - The port number, or null if the given value was not
    
    386
    +   *   valid.
    
    387
    +   */
    
    388
    +  _parsePort(val, trim) {
    
    389
    +    if (typeof val === "string") {
    
    390
    +      if (trim) {
    
    391
    +        val = val.trim();
    
    392
    +      }
    
    393
    +      // ensure port string is a valid positive integer
    
    394
    +      if (this._portRegex.test(val)) {
    
    395
    +        val = Number.parseInt(val, 10);
    
    396
    +      } else {
    
    397
    +        lazy.logger.error(`Invalid port string "${val}"`);
    
    398
    +        return null;
    
    399
    +      }
    
    400
    +    }
    
    401
    +    if (!Number.isInteger(val) || val < 1 || val > 65535) {
    
    402
    +      lazy.logger.error(`Port out of range: ${val}`);
    
    403
    +      return null;
    
    404
    +    }
    
    405
    +    return val;
    
    406
    +  },
    
    407
    +  /**
    
    408
    +   * Test whether two arrays have equal members and order.
    
    409
    +   *
    
    410
    +   * @param {Array} val1 - The first array to test.
    
    411
    +   * @param {Array} val2 - The second array to compare against.
    
    412
    +   *
    
    413
    +   * @return {boolean} - Whether the two arrays are equal.
    
    414
    +   */
    
    415
    +  _arrayEqual(val1, val2) {
    
    416
    +    if (val1.length !== val2.length) {
    
    417
    +      return false;
    
    418
    +    }
    
    419
    +    return val1.every((v, i) => v === val2[i]);
    
    420
    +  },
    
    421
    +
    
    422
    +  /* load or init our settings, and register observers */
    
    423
    +  async init() {
    
    424
    +    this._addProperties("quickstart", {
    
    425
    +      enabled: {},
    
    426
    +    });
    
    427
    +    this._addProperties("bridges", {
    
    428
    +      enabled: {},
    
    429
    +      source: {
    
    430
    +        transform: val => {
    
    431
    +          if (Object.values(TorBridgeSource).includes(val)) {
    
    432
    +            return val;
    
    433
    +          }
    
    434
    +          lazy.logger.error(`Not a valid bridge source: "${val}"`);
    
    435
    +          return TorBridgeSource.Invalid;
    
    436
    +        },
    
    437
    +      },
    
    438
    +      bridge_strings: {
    
    439
    +        transform: val => {
    
    440
    +          if (Array.isArray(val)) {
    
    441
    +            return [...val];
    
    442
    +          }
    
    443
    +          return parseBridgeStrings(val);
    
    444
    +        },
    
    445
    +        copy: val => [...val],
    
    446
    +        equal: (val1, val2) => this._arrayEqual(val1, val2),
    
    447
    +      },
    
    448
    +      builtin_type: {
    
    449
    +        callback: val => {
    
    450
    +          if (!val) {
    
    451
    +            // Make sure that the source is not BuiltIn
    
    452
    +            if (this.bridges.source === TorBridgeSource.BuiltIn) {
    
    453
    +              this.bridges.source = TorBridgeSource.Invalid;
    
    454
    +            }
    
    455
    +            return;
    
    456
    +          }
    
    457
    +          const bridgeStrings = getBuiltinBridgeStrings(val);
    
    458
    +          if (bridgeStrings.length) {
    
    459
    +            this.bridges.bridge_strings = bridgeStrings;
    
    460
    +            return;
    
    461
    +          }
    
    462
    +          lazy.logger.error(`No built-in ${val} bridges found`);
    
    463
    +          // Change to be empty, this will trigger this callback again,
    
    464
    +          // but with val as "".
    
    465
    +          this.bridges.builtin_type == "";
    
    253 466
             },
    
    254
    -        bridges: {
    
    255
    -          enabled: false,
    
    256
    -          source: TorBridgeSource.Invalid,
    
    257
    -          builtin_type: null,
    
    258
    -          bridge_strings: [],
    
    467
    +      },
    
    468
    +    });
    
    469
    +    this._addProperties("proxy", {
    
    470
    +      enabled: {
    
    471
    +        callback: val => {
    
    472
    +          if (val) {
    
    473
    +            return;
    
    474
    +          }
    
    475
    +          // Reset proxy settings.
    
    476
    +          this.proxy.type = TorProxyType.Invalid;
    
    477
    +          this.proxy.address = "";
    
    478
    +          this.proxy.port = 0;
    
    479
    +          this.proxy.username = "";
    
    480
    +          this.proxy.password = "";
    
    259 481
             },
    
    260
    -        proxy: {
    
    261
    -          enabled: false,
    
    262
    -          type: TorProxyType.Invalid,
    
    263
    -          address: null,
    
    264
    -          port: 0,
    
    265
    -          username: null,
    
    266
    -          password: null,
    
    482
    +      },
    
    483
    +      type: {
    
    484
    +        transform: val => {
    
    485
    +          if (Object.values(TorProxyType).includes(val)) {
    
    486
    +            return val;
    
    487
    +          }
    
    488
    +          lazy.logger.error(`Not a valid proxy type: "${val}"`);
    
    489
    +          return TorProxyType.Invalid;
    
    267 490
             },
    
    268
    -        firewall: {
    
    269
    -          enabled: false,
    
    270
    -          allowed_ports: [],
    
    491
    +      },
    
    492
    +      address: {},
    
    493
    +      port: {
    
    494
    +        transform: val => {
    
    495
    +          if (val === 0) {
    
    496
    +            // This is a valid value that "unsets" the port.
    
    497
    +            // Keep this value without giving a warning.
    
    498
    +            // NOTE: In contrast, "0" is not valid.
    
    499
    +            return 0;
    
    500
    +          }
    
    501
    +          // Unset to 0 if invalid null is returned.
    
    502
    +          return this._parsePort(val, false) ?? 0;
    
    271 503
             },
    
    272
    -      };
    
    273
    -      return settings;
    
    274
    -    },
    
    275
    -
    
    276
    -    /* load or init our settings, and register observers */
    
    277
    -    async init() {
    
    278
    -      // TODO: We could use a shared promise, and wait for it to be fullfilled
    
    279
    -      // instead of Service.obs.
    
    280
    -      if (lazy.TorLauncherUtil.shouldStartAndOwnTor) {
    
    281
    -        // if the settings branch exists, load settings from prefs
    
    282
    -        if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
    
    504
    +      },
    
    505
    +      username: {},
    
    506
    +      password: {},
    
    507
    +      uri: {
    
    508
    +        getter: () => {
    
    509
    +          const { type, address, port, username, password } = this.proxy;
    
    510
    +          switch (type) {
    
    511
    +            case TorProxyType.Socks4:
    
    512
    +              return `socks4a://${address}:${port}`;
    
    513
    +            case TorProxyType.Socks5:
    
    514
    +              if (username) {
    
    515
    +                return `socks5://${username}:${password}@${address}:${port}`;
    
    516
    +              }
    
    517
    +              return `socks5://${address}:${port}`;
    
    518
    +            case TorProxyType.HTTPS:
    
    519
    +              if (username) {
    
    520
    +                return `http://${username}:${password}@${address}:${port}`;
    
    521
    +              }
    
    522
    +              return `http://${address}:${port}`;
    
    523
    +          }
    
    524
    +          return null;
    
    525
    +        },
    
    526
    +      },
    
    527
    +    });
    
    528
    +    this._addProperties("firewall", {
    
    529
    +      enabled: {
    
    530
    +        callback: val => {
    
    531
    +          if (!val) {
    
    532
    +            this.firewall.allowed_ports = "";
    
    533
    +          }
    
    534
    +        },
    
    535
    +      },
    
    536
    +      allowed_ports: {
    
    537
    +        transform: val => {
    
    538
    +          if (!Array.isArray(val)) {
    
    539
    +            val = val === "" ? [] : val.split(",");
    
    540
    +          }
    
    541
    +          // parse and remove duplicates
    
    542
    +          const portSet = new Set(val.map(p => this._parsePort(p, true)));
    
    543
    +          // parsePort returns null for failed parses, so remove it.
    
    544
    +          portSet.delete(null);
    
    545
    +          return [...portSet];
    
    546
    +        },
    
    547
    +        copy: val => [...val],
    
    548
    +        equal: (val1, val2) => this._arrayEqual(val1, val2),
    
    549
    +      },
    
    550
    +    });
    
    551
    +
    
    552
    +    // TODO: We could use a shared promise, and wait for it to be fullfilled
    
    553
    +    // instead of Service.obs.
    
    554
    +    if (lazy.TorLauncherUtil.shouldStartAndOwnTor) {
    
    555
    +      // if the settings branch exists, load settings from prefs
    
    556
    +      if (Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)) {
    
    557
    +        // Do not want notifications for initially loaded prefs.
    
    558
    +        this.freezeNotifications();
    
    559
    +        try {
    
    283 560
               this.loadFromPrefs();
    
    284
    -        } else {
    
    285
    -          // otherwise load defaults
    
    286
    -          this._settings = this.defaultSettings();
    
    561
    +        } finally {
    
    562
    +          this._notificationQueue.clear();
    
    563
    +          this.thawNotifications();
    
    287 564
             }
    
    288
    -        Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
    
    289
    -
    
    290
    -        try {
    
    291
    -          const provider = await lazy.TorProviderBuilder.build();
    
    292
    -          if (provider.isRunning) {
    
    293
    -            this.handleProcessReady();
    
    294
    -          }
    
    295
    -        } catch {}
    
    296 565
           }
    
    297
    -    },
    
    566
    +      try {
    
    567
    +        const provider = await lazy.TorProviderBuilder.build();
    
    568
    +        if (provider.isRunning) {
    
    569
    +          this.handleProcessReady();
    
    570
    +          // No need to add an observer to call this again.
    
    571
    +          return;
    
    572
    +        }
    
    573
    +      } catch {}
    
    298 574
     
    
    299
    -    /* wait for relevant life-cycle events to apply saved settings */
    
    300
    -    async observe(subject, topic, data) {
    
    301
    -      console.log(`TorSettings: Observed ${topic}`);
    
    575
    +      Services.obs.addObserver(this, lazy.TorProviderTopics.ProcessIsReady);
    
    576
    +    }
    
    577
    +  },
    
    302 578
     
    
    303
    -      switch (topic) {
    
    304
    -        case lazy.TorProviderTopics.ProcessIsReady:
    
    305
    -          Services.obs.removeObserver(
    
    306
    -            this,
    
    307
    -            lazy.TorProviderTopics.ProcessIsReady
    
    308
    -          );
    
    309
    -          await this.handleProcessReady();
    
    310
    -          break;
    
    311
    -      }
    
    312
    -    },
    
    579
    +  /* wait for relevant life-cycle events to apply saved settings */
    
    580
    +  async observe(subject, topic, data) {
    
    581
    +    lazy.logger.debug(`Observed ${topic}`);
    
    313 582
     
    
    314
    -    // once the tor daemon is ready, we need to apply our settings
    
    315
    -    async handleProcessReady() {
    
    316
    -      // push down settings to tor
    
    317
    -      await this.applySettings();
    
    318
    -      console.log("TorSettings: Ready");
    
    319
    -      Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
    
    320
    -    },
    
    583
    +    switch (topic) {
    
    584
    +      case lazy.TorProviderTopics.ProcessIsReady:
    
    585
    +        Services.obs.removeObserver(
    
    586
    +          this,
    
    587
    +          lazy.TorProviderTopics.ProcessIsReady
    
    588
    +        );
    
    589
    +        await this.handleProcessReady();
    
    590
    +        break;
    
    591
    +    }
    
    592
    +  },
    
    321 593
     
    
    322
    -    // load our settings from prefs
    
    323
    -    loadFromPrefs() {
    
    324
    -      console.log("TorSettings: loadFromPrefs()");
    
    594
    +  // once the tor daemon is ready, we need to apply our settings
    
    595
    +  async handleProcessReady() {
    
    596
    +    // push down settings to tor
    
    597
    +    await this.applySettings();
    
    598
    +    lazy.logger.info("Ready");
    
    599
    +    Services.obs.notifyObservers(null, TorSettingsTopics.Ready);
    
    600
    +  },
    
    325 601
     
    
    326
    -      const settings = this.defaultSettings();
    
    602
    +  // load our settings from prefs
    
    603
    +  loadFromPrefs() {
    
    604
    +    lazy.logger.debug("loadFromPrefs()");
    
    327 605
     
    
    328
    -      /* Quickstart */
    
    329
    -      settings.quickstart.enabled = Services.prefs.getBoolPref(
    
    330
    -        TorSettingsPrefs.quickstart.enabled,
    
    331
    -        false
    
    606
    +    /* Quickstart */
    
    607
    +    this.quickstart.enabled = Services.prefs.getBoolPref(
    
    608
    +      TorSettingsPrefs.quickstart.enabled,
    
    609
    +      false
    
    610
    +    );
    
    611
    +    /* Bridges */
    
    612
    +    this.bridges.enabled = Services.prefs.getBoolPref(
    
    613
    +      TorSettingsPrefs.bridges.enabled,
    
    614
    +      false
    
    615
    +    );
    
    616
    +    this.bridges.source = Services.prefs.getIntPref(
    
    617
    +      TorSettingsPrefs.bridges.source,
    
    618
    +      TorBridgeSource.Invalid
    
    619
    +    );
    
    620
    +    if (this.bridges.source == TorBridgeSource.BuiltIn) {
    
    621
    +      this.bridges.builtin_type = Services.prefs.getStringPref(
    
    622
    +        TorSettingsPrefs.bridges.builtin_type,
    
    623
    +        ""
    
    332 624
           );
    
    333
    -      /* Bridges */
    
    334
    -      settings.bridges.enabled = Services.prefs.getBoolPref(
    
    335
    -        TorSettingsPrefs.bridges.enabled,
    
    336
    -        false
    
    625
    +    } else {
    
    626
    +      const bridgeBranchPrefs = Services.prefs
    
    627
    +        .getBranch(TorSettingsPrefs.bridges.bridge_strings)
    
    628
    +        .getChildList("");
    
    629
    +      this.bridges.bridge_strings = Array.from(bridgeBranchPrefs, pref =>
    
    630
    +        Services.prefs.getStringPref(
    
    631
    +          `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
    
    632
    +        )
    
    337 633
           );
    
    338
    -      settings.bridges.source = Services.prefs.getIntPref(
    
    339
    -        TorSettingsPrefs.bridges.source,
    
    340
    -        TorBridgeSource.Invalid
    
    634
    +    }
    
    635
    +    /* Proxy */
    
    636
    +    this.proxy.enabled = Services.prefs.getBoolPref(
    
    637
    +      TorSettingsPrefs.proxy.enabled,
    
    638
    +      false
    
    639
    +    );
    
    640
    +    if (this.proxy.enabled) {
    
    641
    +      this.proxy.type = Services.prefs.getIntPref(
    
    642
    +        TorSettingsPrefs.proxy.type,
    
    643
    +        TorProxyType.Invalid
    
    341 644
           );
    
    342
    -      if (settings.bridges.source == TorBridgeSource.BuiltIn) {
    
    343
    -        const builtinType = Services.prefs.getStringPref(
    
    344
    -          TorSettingsPrefs.bridges.builtin_type,
    
    345
    -          ""
    
    346
    -        );
    
    347
    -        settings.bridges.builtin_type = builtinType;
    
    348
    -        settings.bridges.bridge_strings = getBuiltinBridgeStrings(builtinType);
    
    349
    -        if (!settings.bridges.bridge_strings.length) {
    
    350
    -          // in this case the user is using a builtin bridge that is no longer supported,
    
    351
    -          // reset to settings to default values
    
    352
    -          console.warn(
    
    353
    -            `[TorSettings] Cannot find any bridge line for the configured bridge type ${builtinType}`
    
    354
    -          );
    
    355
    -          settings.bridges.source = TorBridgeSource.Invalid;
    
    356
    -          settings.bridges.builtin_type = null;
    
    357
    -        }
    
    358
    -      } else {
    
    359
    -        settings.bridges.bridge_strings = [];
    
    360
    -        const bridgeBranchPrefs = Services.prefs
    
    361
    -          .getBranch(TorSettingsPrefs.bridges.bridge_strings)
    
    362
    -          .getChildList("");
    
    363
    -        bridgeBranchPrefs.forEach(pref => {
    
    364
    -          const bridgeString = Services.prefs.getStringPref(
    
    365
    -            `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
    
    366
    -          );
    
    367
    -          settings.bridges.bridge_strings.push(bridgeString);
    
    368
    -        });
    
    369
    -      }
    
    370
    -      /* Proxy */
    
    371
    -      settings.proxy.enabled = Services.prefs.getBoolPref(
    
    372
    -        TorSettingsPrefs.proxy.enabled,
    
    373
    -        false
    
    645
    +      this.proxy.address = Services.prefs.getStringPref(
    
    646
    +        TorSettingsPrefs.proxy.address,
    
    647
    +        ""
    
    374 648
           );
    
    375
    -      if (settings.proxy.enabled) {
    
    376
    -        settings.proxy.type = Services.prefs.getIntPref(
    
    377
    -          TorSettingsPrefs.proxy.type,
    
    378
    -          TorProxyType.Invalid
    
    379
    -        );
    
    380
    -        settings.proxy.address = Services.prefs.getStringPref(
    
    381
    -          TorSettingsPrefs.proxy.address,
    
    382
    -          ""
    
    383
    -        );
    
    384
    -        settings.proxy.port = Services.prefs.getIntPref(
    
    385
    -          TorSettingsPrefs.proxy.port,
    
    386
    -          0
    
    387
    -        );
    
    388
    -        settings.proxy.username = Services.prefs.getStringPref(
    
    389
    -          TorSettingsPrefs.proxy.username,
    
    390
    -          ""
    
    391
    -        );
    
    392
    -        settings.proxy.password = Services.prefs.getStringPref(
    
    393
    -          TorSettingsPrefs.proxy.password,
    
    394
    -          ""
    
    395
    -        );
    
    396
    -      } else {
    
    397
    -        settings.proxy.type = TorProxyType.Invalid;
    
    398
    -        settings.proxy.address = null;
    
    399
    -        settings.proxy.port = 0;
    
    400
    -        settings.proxy.username = null;
    
    401
    -        settings.proxy.password = null;
    
    402
    -      }
    
    403
    -
    
    404
    -      /* Firewall */
    
    405
    -      settings.firewall.enabled = Services.prefs.getBoolPref(
    
    406
    -        TorSettingsPrefs.firewall.enabled,
    
    407
    -        false
    
    649
    +      this.proxy.port = Services.prefs.getIntPref(
    
    650
    +        TorSettingsPrefs.proxy.port,
    
    651
    +        0
    
    408 652
           );
    
    409
    -      if (settings.firewall.enabled) {
    
    410
    -        const portList = Services.prefs.getStringPref(
    
    411
    -          TorSettingsPrefs.firewall.allowed_ports,
    
    412
    -          ""
    
    413
    -        );
    
    414
    -        settings.firewall.allowed_ports = parsePortList(portList);
    
    415
    -      } else {
    
    416
    -        settings.firewall.allowed_ports = 0;
    
    417
    -      }
    
    418
    -
    
    419
    -      this._settings = settings;
    
    420
    -
    
    421
    -      return this;
    
    422
    -    },
    
    653
    +      this.proxy.username = Services.prefs.getStringPref(
    
    654
    +        TorSettingsPrefs.proxy.username,
    
    655
    +        ""
    
    656
    +      );
    
    657
    +      this.proxy.password = Services.prefs.getStringPref(
    
    658
    +        TorSettingsPrefs.proxy.password,
    
    659
    +        ""
    
    660
    +      );
    
    661
    +    }
    
    423 662
     
    
    424
    -    // save our settings to prefs
    
    425
    -    saveToPrefs() {
    
    426
    -      console.log("TorSettings: saveToPrefs()");
    
    663
    +    /* Firewall */
    
    664
    +    this.firewall.enabled = Services.prefs.getBoolPref(
    
    665
    +      TorSettingsPrefs.firewall.enabled,
    
    666
    +      false
    
    667
    +    );
    
    668
    +    if (this.firewall.enabled) {
    
    669
    +      this.firewall.allowed_ports = Services.prefs.getStringPref(
    
    670
    +        TorSettingsPrefs.firewall.allowed_ports,
    
    671
    +        ""
    
    672
    +      );
    
    673
    +    }
    
    674
    +  },
    
    427 675
     
    
    428
    -      const settings = this._settings;
    
    676
    +  // save our settings to prefs
    
    677
    +  saveToPrefs() {
    
    678
    +    lazy.logger.debug("saveToPrefs()");
    
    429 679
     
    
    430
    -      /* Quickstart */
    
    431
    -      Services.prefs.setBoolPref(
    
    432
    -        TorSettingsPrefs.quickstart.enabled,
    
    433
    -        settings.quickstart.enabled
    
    434
    -      );
    
    435
    -      /* Bridges */
    
    436
    -      Services.prefs.setBoolPref(
    
    437
    -        TorSettingsPrefs.bridges.enabled,
    
    438
    -        settings.bridges.enabled
    
    680
    +    /* Quickstart */
    
    681
    +    Services.prefs.setBoolPref(
    
    682
    +      TorSettingsPrefs.quickstart.enabled,
    
    683
    +      this.quickstart.enabled
    
    684
    +    );
    
    685
    +    /* Bridges */
    
    686
    +    Services.prefs.setBoolPref(
    
    687
    +      TorSettingsPrefs.bridges.enabled,
    
    688
    +      this.bridges.enabled
    
    689
    +    );
    
    690
    +    Services.prefs.setIntPref(
    
    691
    +      TorSettingsPrefs.bridges.source,
    
    692
    +      this.bridges.source
    
    693
    +    );
    
    694
    +    Services.prefs.setStringPref(
    
    695
    +      TorSettingsPrefs.bridges.builtin_type,
    
    696
    +      this.bridges.builtin_type
    
    697
    +    );
    
    698
    +    // erase existing bridge strings
    
    699
    +    const bridgeBranchPrefs = Services.prefs
    
    700
    +      .getBranch(TorSettingsPrefs.bridges.bridge_strings)
    
    701
    +      .getChildList("");
    
    702
    +    bridgeBranchPrefs.forEach(pref => {
    
    703
    +      Services.prefs.clearUserPref(
    
    704
    +        `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
    
    439 705
           );
    
    440
    -      Services.prefs.setIntPref(
    
    441
    -        TorSettingsPrefs.bridges.source,
    
    442
    -        settings.bridges.source
    
    706
    +    });
    
    707
    +    // write new ones
    
    708
    +    if (this.bridges.source !== TorBridgeSource.BuiltIn) {
    
    709
    +      this.bridges.bridge_strings.forEach((string, index) => {
    
    710
    +        Services.prefs.setStringPref(
    
    711
    +          `${TorSettingsPrefs.bridges.bridge_strings}.${index}`,
    
    712
    +          string
    
    713
    +        );
    
    714
    +      });
    
    715
    +    }
    
    716
    +    /* Proxy */
    
    717
    +    Services.prefs.setBoolPref(
    
    718
    +      TorSettingsPrefs.proxy.enabled,
    
    719
    +      this.proxy.enabled
    
    720
    +    );
    
    721
    +    if (this.proxy.enabled) {
    
    722
    +      Services.prefs.setIntPref(TorSettingsPrefs.proxy.type, this.proxy.type);
    
    723
    +      Services.prefs.setStringPref(
    
    724
    +        TorSettingsPrefs.proxy.address,
    
    725
    +        this.proxy.address
    
    443 726
           );
    
    727
    +      Services.prefs.setIntPref(TorSettingsPrefs.proxy.port, this.proxy.port);
    
    444 728
           Services.prefs.setStringPref(
    
    445
    -        TorSettingsPrefs.bridges.builtin_type,
    
    446
    -        settings.bridges.builtin_type
    
    729
    +        TorSettingsPrefs.proxy.username,
    
    730
    +        this.proxy.username
    
    447 731
           );
    
    448
    -      // erase existing bridge strings
    
    449
    -      const bridgeBranchPrefs = Services.prefs
    
    450
    -        .getBranch(TorSettingsPrefs.bridges.bridge_strings)
    
    451
    -        .getChildList("");
    
    452
    -      bridgeBranchPrefs.forEach(pref => {
    
    453
    -        Services.prefs.clearUserPref(
    
    454
    -          `${TorSettingsPrefs.bridges.bridge_strings}${pref}`
    
    455
    -        );
    
    456
    -      });
    
    457
    -      // write new ones
    
    458
    -      if (settings.bridges.source !== TorBridgeSource.BuiltIn) {
    
    459
    -        settings.bridges.bridge_strings.forEach((string, index) => {
    
    460
    -          Services.prefs.setStringPref(
    
    461
    -            `${TorSettingsPrefs.bridges.bridge_strings}.${index}`,
    
    462
    -            string
    
    463
    -          );
    
    464
    -        });
    
    465
    -      }
    
    466
    -      /* Proxy */
    
    467
    -      Services.prefs.setBoolPref(
    
    468
    -        TorSettingsPrefs.proxy.enabled,
    
    469
    -        settings.proxy.enabled
    
    732
    +      Services.prefs.setStringPref(
    
    733
    +        TorSettingsPrefs.proxy.password,
    
    734
    +        this.proxy.password
    
    470 735
           );
    
    471
    -      if (settings.proxy.enabled) {
    
    472
    -        Services.prefs.setIntPref(
    
    473
    -          TorSettingsPrefs.proxy.type,
    
    474
    -          settings.proxy.type
    
    475
    -        );
    
    476
    -        Services.prefs.setStringPref(
    
    477
    -          TorSettingsPrefs.proxy.address,
    
    478
    -          settings.proxy.address
    
    479
    -        );
    
    480
    -        Services.prefs.setIntPref(
    
    481
    -          TorSettingsPrefs.proxy.port,
    
    482
    -          settings.proxy.port
    
    483
    -        );
    
    484
    -        Services.prefs.setStringPref(
    
    485
    -          TorSettingsPrefs.proxy.username,
    
    486
    -          settings.proxy.username
    
    487
    -        );
    
    488
    -        Services.prefs.setStringPref(
    
    489
    -          TorSettingsPrefs.proxy.password,
    
    490
    -          settings.proxy.password
    
    491
    -        );
    
    492
    -      } else {
    
    493
    -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type);
    
    494
    -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address);
    
    495
    -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port);
    
    496
    -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username);
    
    497
    -        Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password);
    
    498
    -      }
    
    499
    -      /* Firewall */
    
    500
    -      Services.prefs.setBoolPref(
    
    501
    -        TorSettingsPrefs.firewall.enabled,
    
    502
    -        settings.firewall.enabled
    
    736
    +    } else {
    
    737
    +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type);
    
    738
    +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.address);
    
    739
    +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.port);
    
    740
    +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.username);
    
    741
    +      Services.prefs.clearUserPref(TorSettingsPrefs.proxy.password);
    
    742
    +    }
    
    743
    +    /* Firewall */
    
    744
    +    Services.prefs.setBoolPref(
    
    745
    +      TorSettingsPrefs.firewall.enabled,
    
    746
    +      this.firewall.enabled
    
    747
    +    );
    
    748
    +    if (this.firewall.enabled) {
    
    749
    +      Services.prefs.setStringPref(
    
    750
    +        TorSettingsPrefs.firewall.allowed_ports,
    
    751
    +        this.firewall.allowed_ports.join(",")
    
    503 752
           );
    
    504
    -      if (settings.firewall.enabled) {
    
    505
    -        Services.prefs.setStringPref(
    
    506
    -          TorSettingsPrefs.firewall.allowed_ports,
    
    507
    -          settings.firewall.allowed_ports.join(",")
    
    508
    -        );
    
    509
    -      } else {
    
    510
    -        Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports);
    
    511
    -      }
    
    753
    +    } else {
    
    754
    +      Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports);
    
    755
    +    }
    
    512 756
     
    
    513
    -      // all tor settings now stored in prefs :)
    
    514
    -      Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true);
    
    757
    +    // all tor settings now stored in prefs :)
    
    758
    +    Services.prefs.setBoolPref(TorSettingsPrefs.enabled, true);
    
    515 759
     
    
    516
    -      return this;
    
    517
    -    },
    
    518
    -
    
    519
    -    // push our settings down to the tor daemon
    
    520
    -    async applySettings() {
    
    521
    -      console.log("TorSettings: applySettings()");
    
    522
    -      const settings = this._settings;
    
    523
    -      const settingsMap = new Map();
    
    524
    -
    
    525
    -      /* Bridges */
    
    526
    -      const haveBridges =
    
    527
    -        settings.bridges.enabled && !!settings.bridges.bridge_strings.length;
    
    528
    -      settingsMap.set(TorConfigKeys.useBridges, haveBridges);
    
    529
    -      if (haveBridges) {
    
    530
    -        settingsMap.set(
    
    531
    -          TorConfigKeys.bridgeList,
    
    532
    -          settings.bridges.bridge_strings
    
    533
    -        );
    
    534
    -      } else {
    
    535
    -        settingsMap.set(TorConfigKeys.bridgeList, null);
    
    536
    -      }
    
    760
    +    return this;
    
    761
    +  },
    
    537 762
     
    
    538
    -      /* Proxy */
    
    539
    -      settingsMap.set(TorConfigKeys.socks4Proxy, null);
    
    540
    -      settingsMap.set(TorConfigKeys.socks5Proxy, null);
    
    541
    -      settingsMap.set(TorConfigKeys.socks5ProxyUsername, null);
    
    542
    -      settingsMap.set(TorConfigKeys.socks5ProxyPassword, null);
    
    543
    -      settingsMap.set(TorConfigKeys.httpsProxy, null);
    
    544
    -      settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null);
    
    545
    -      if (settings.proxy.enabled) {
    
    546
    -        const address = settings.proxy.address;
    
    547
    -        const port = settings.proxy.port;
    
    548
    -        const username = settings.proxy.username;
    
    549
    -        const password = settings.proxy.password;
    
    550
    -
    
    551
    -        switch (settings.proxy.type) {
    
    552
    -          case TorProxyType.Socks4:
    
    553
    -            settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`);
    
    554
    -            break;
    
    555
    -          case TorProxyType.Socks5:
    
    556
    -            settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`);
    
    557
    -            settingsMap.set(TorConfigKeys.socks5ProxyUsername, username);
    
    558
    -            settingsMap.set(TorConfigKeys.socks5ProxyPassword, password);
    
    559
    -            break;
    
    560
    -          case TorProxyType.HTTPS:
    
    561
    -            settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`);
    
    562
    -            settingsMap.set(
    
    563
    -              TorConfigKeys.httpsProxyAuthenticator,
    
    564
    -              `${username}:${password}`
    
    565
    -            );
    
    566
    -            break;
    
    567
    -        }
    
    568
    -      }
    
    763
    +  // push our settings down to the tor daemon
    
    764
    +  async applySettings() {
    
    765
    +    lazy.logger.debug("applySettings()");
    
    766
    +    const settingsMap = new Map();
    
    767
    +
    
    768
    +    /* Bridges */
    
    769
    +    const haveBridges =
    
    770
    +      this.bridges.enabled && !!this.bridges.bridge_strings.length;
    
    771
    +    settingsMap.set(TorConfigKeys.useBridges, haveBridges);
    
    772
    +    if (haveBridges) {
    
    773
    +      settingsMap.set(TorConfigKeys.bridgeList, this.bridges.bridge_strings);
    
    774
    +    } else {
    
    775
    +      settingsMap.set(TorConfigKeys.bridgeList, null);
    
    776
    +    }
    
    569 777
     
    
    570
    -      /* Firewall */
    
    571
    -      if (settings.firewall.enabled) {
    
    572
    -        const reachableAddresses = settings.firewall.allowed_ports
    
    573
    -          .map(port => `*:${port}`)
    
    574
    -          .join(",");
    
    575
    -        settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses);
    
    576
    -      } else {
    
    577
    -        settingsMap.set(TorConfigKeys.reachableAddresses, null);
    
    778
    +    /* Proxy */
    
    779
    +    settingsMap.set(TorConfigKeys.socks4Proxy, null);
    
    780
    +    settingsMap.set(TorConfigKeys.socks5Proxy, null);
    
    781
    +    settingsMap.set(TorConfigKeys.socks5ProxyUsername, null);
    
    782
    +    settingsMap.set(TorConfigKeys.socks5ProxyPassword, null);
    
    783
    +    settingsMap.set(TorConfigKeys.httpsProxy, null);
    
    784
    +    settingsMap.set(TorConfigKeys.httpsProxyAuthenticator, null);
    
    785
    +    if (this.proxy.enabled) {
    
    786
    +      const address = this.proxy.address;
    
    787
    +      const port = this.proxy.port;
    
    788
    +      const username = this.proxy.username;
    
    789
    +      const password = this.proxy.password;
    
    790
    +
    
    791
    +      switch (this.proxy.type) {
    
    792
    +        case TorProxyType.Socks4:
    
    793
    +          settingsMap.set(TorConfigKeys.socks4Proxy, `${address}:${port}`);
    
    794
    +          break;
    
    795
    +        case TorProxyType.Socks5:
    
    796
    +          settingsMap.set(TorConfigKeys.socks5Proxy, `${address}:${port}`);
    
    797
    +          settingsMap.set(TorConfigKeys.socks5ProxyUsername, username);
    
    798
    +          settingsMap.set(TorConfigKeys.socks5ProxyPassword, password);
    
    799
    +          break;
    
    800
    +        case TorProxyType.HTTPS:
    
    801
    +          settingsMap.set(TorConfigKeys.httpsProxy, `${address}:${port}`);
    
    802
    +          settingsMap.set(
    
    803
    +            TorConfigKeys.httpsProxyAuthenticator,
    
    804
    +            `${username}:${password}`
    
    805
    +          );
    
    806
    +          break;
    
    578 807
           }
    
    808
    +    }
    
    579 809
     
    
    580
    -      /* Push to Tor */
    
    581
    -      const provider = await lazy.TorProviderBuilder.build();
    
    582
    -      await provider.writeSettings(settingsMap);
    
    810
    +    /* Firewall */
    
    811
    +    if (this.firewall.enabled) {
    
    812
    +      const reachableAddresses = this.firewall.allowed_ports
    
    813
    +        .map(port => `*:${port}`)
    
    814
    +        .join(",");
    
    815
    +      settingsMap.set(TorConfigKeys.reachableAddresses, reachableAddresses);
    
    816
    +    } else {
    
    817
    +      settingsMap.set(TorConfigKeys.reachableAddresses, null);
    
    818
    +    }
    
    583 819
     
    
    584
    -      return this;
    
    585
    -    },
    
    820
    +    /* Push to Tor */
    
    821
    +    const provider = await lazy.TorProviderBuilder.build();
    
    822
    +    await provider.writeSettings(settingsMap);
    
    586 823
     
    
    587
    -    // set all of our settings at once from a settings object
    
    588
    -    setSettings(settings) {
    
    589
    -      console.log("TorSettings: setSettings()");
    
    590
    -      const backup = this.getSettings();
    
    824
    +    return this;
    
    825
    +  },
    
    591 826
     
    
    592
    -      try {
    
    593
    -        this._settings.bridges.enabled = !!settings.bridges.enabled;
    
    594
    -        this._settings.bridges.source = settings.bridges.source;
    
    595
    -        switch (settings.bridges.source) {
    
    596
    -          case TorBridgeSource.BridgeDB:
    
    597
    -          case TorBridgeSource.UserProvided:
    
    598
    -            this._settings.bridges.bridge_strings =
    
    599
    -              settings.bridges.bridge_strings;
    
    600
    -            break;
    
    601
    -          case TorBridgeSource.BuiltIn: {
    
    602
    -            this._settings.bridges.builtin_type = settings.bridges.builtin_type;
    
    603
    -            settings.bridges.bridge_strings = getBuiltinBridgeStrings(
    
    604
    -              settings.bridges.builtin_type
    
    827
    +  // set all of our settings at once from a settings object
    
    828
    +  setSettings(settings) {
    
    829
    +    lazy.logger.debug("setSettings()");
    
    830
    +    const backup = this.getSettings();
    
    831
    +    const backup_notifications = [...this._notificationQueue];
    
    832
    +
    
    833
    +    // Hold off on lots of notifications until all settings are changed.
    
    834
    +    this.freezeNotifications();
    
    835
    +    try {
    
    836
    +      this.bridges.enabled = !!settings.bridges.enabled;
    
    837
    +      this.bridges.source = settings.bridges.source;
    
    838
    +      switch (settings.bridges.source) {
    
    839
    +        case TorBridgeSource.BridgeDB:
    
    840
    +        case TorBridgeSource.UserProvided:
    
    841
    +          this.bridges.bridge_strings = settings.bridges.bridge_strings;
    
    842
    +          break;
    
    843
    +        case TorBridgeSource.BuiltIn: {
    
    844
    +          this.bridges.builtin_type = settings.bridges.builtin_type;
    
    845
    +          if (!this.bridges.bridge_strings.length) {
    
    846
    +            // No bridges were found when setting the builtin_type.
    
    847
    +            throw new Error(
    
    848
    +              `No available builtin bridges of type ${settings.bridges.builtin_type}`
    
    605 849
                 );
    
    606
    -            if (
    
    607
    -              !settings.bridges.bridge_strings.length &&
    
    608
    -              settings.bridges.enabled
    
    609
    -            ) {
    
    610
    -              throw new Error(
    
    611
    -                `No available builtin bridges of type ${settings.bridges.builtin_type}`
    
    612
    -              );
    
    613
    -            }
    
    614
    -            this._settings.bridges.bridge_strings =
    
    615
    -              settings.bridges.bridge_strings;
    
    616
    -            break;
    
    617 850
               }
    
    618
    -          case TorBridgeSource.Invalid:
    
    619
    -            break;
    
    620
    -          default:
    
    621
    -            if (settings.bridges.enabled) {
    
    622
    -              throw new Error(
    
    623
    -                `Bridge source '${settings.source}' is not a valid source`
    
    624
    -              );
    
    625
    -            }
    
    626
    -            break;
    
    851
    +          break;
    
    627 852
             }
    
    628
    -
    
    629
    -        // TODO: proxy and firewall
    
    630
    -      } catch (ex) {
    
    631
    -        this._settings = backup;
    
    632
    -        console.log(`TorSettings: setSettings failed => ${ex.message}`);
    
    633
    -      }
    
    634
    -
    
    635
    -      console.log("TorSettings: setSettings result");
    
    636
    -      console.log(this._settings);
    
    637
    -    },
    
    638
    -
    
    639
    -    // get a copy of all our settings
    
    640
    -    getSettings() {
    
    641
    -      console.log("TorSettings: getSettings()");
    
    642
    -      // TODO: replace with structuredClone someday (post esr94): https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
    
    643
    -      return JSON.parse(JSON.stringify(this._settings));
    
    644
    -    },
    
    645
    -
    
    646
    -    /* Getters and Setters */
    
    647
    -
    
    648
    -    // Quickstart
    
    649
    -    get quickstart() {
    
    650
    -      return {
    
    651
    -        get enabled() {
    
    652
    -          return self._settings.quickstart.enabled;
    
    653
    -        },
    
    654
    -        set enabled(val) {
    
    655
    -          if (val != self._settings.quickstart.enabled) {
    
    656
    -            self._settings.quickstart.enabled = val;
    
    657
    -            Services.obs.notifyObservers(
    
    658
    -              { value: val },
    
    659
    -              TorSettingsTopics.SettingChanged,
    
    660
    -              TorSettingsData.QuickStartEnabled
    
    853
    +        case TorBridgeSource.Invalid:
    
    854
    +          break;
    
    855
    +        default:
    
    856
    +          if (settings.bridges.enabled) {
    
    857
    +            throw new Error(
    
    858
    +              `Bridge source '${settings.source}' is not a valid source`
    
    661 859
                 );
    
    662 860
               }
    
    663
    -        },
    
    664
    -      };
    
    665
    -    },
    
    861
    +          break;
    
    862
    +      }
    
    666 863
     
    
    667
    -    // Bridges
    
    668
    -    get bridges() {
    
    669
    -      return {
    
    670
    -        get enabled() {
    
    671
    -          return self._settings.bridges.enabled;
    
    672
    -        },
    
    673
    -        set enabled(val) {
    
    674
    -          self._settings.bridges.enabled = val;
    
    675
    -        },
    
    676
    -        get source() {
    
    677
    -          return self._settings.bridges.source;
    
    678
    -        },
    
    679
    -        set source(val) {
    
    680
    -          self._settings.bridges.source = val;
    
    681
    -        },
    
    682
    -        get builtin_type() {
    
    683
    -          return self._settings.bridges.builtin_type;
    
    684
    -        },
    
    685
    -        set builtin_type(val) {
    
    686
    -          const bridgeStrings = getBuiltinBridgeStrings(val);
    
    687
    -          if (bridgeStrings.length) {
    
    688
    -            self._settings.bridges.builtin_type = val;
    
    689
    -            self._settings.bridges.bridge_strings = bridgeStrings;
    
    690
    -          } else {
    
    691
    -            self._settings.bridges.builtin_type = "";
    
    692
    -            if (self._settings.bridges.source === TorBridgeSource.BuiltIn) {
    
    693
    -              self._settings.bridges.source = TorBridgeSource.Invalid;
    
    694
    -            }
    
    695
    -          }
    
    696
    -        },
    
    697
    -        get bridge_strings() {
    
    698
    -          return arrayCopy(self._settings.bridges.bridge_strings);
    
    699
    -        },
    
    700
    -        set bridge_strings(val) {
    
    701
    -          self._settings.bridges.bridge_strings = parseBridgeStrings(val);
    
    702
    -        },
    
    703
    -      };
    
    704
    -    },
    
    864
    +      // TODO: proxy and firewall
    
    865
    +    } catch (ex) {
    
    866
    +      // Restore the old settings without any new notifications generated from
    
    867
    +      // the above code.
    
    868
    +      // NOTE: Since this code is not async, it should not be possible for
    
    869
    +      // some other call to TorSettings to change anything whilst we are
    
    870
    +      // in this context (other than lower down in this call stack), so it is
    
    871
    +      // safe to discard all changes to settings and notifications.
    
    872
    +      this._settings = backup;
    
    873
    +      this._notificationQueue.clear();
    
    874
    +      for (const notification of backup_notifications) {
    
    875
    +        this._notificationQueue.add(notification);
    
    876
    +      }
    
    705 877
     
    
    706
    -    // Proxy
    
    707
    -    get proxy() {
    
    708
    -      return {
    
    709
    -        get enabled() {
    
    710
    -          return self._settings.proxy.enabled;
    
    711
    -        },
    
    712
    -        set enabled(val) {
    
    713
    -          self._settings.proxy.enabled = val;
    
    714
    -          // reset proxy settings
    
    715
    -          self._settings.proxy.type = TorProxyType.Invalid;
    
    716
    -          self._settings.proxy.address = null;
    
    717
    -          self._settings.proxy.port = 0;
    
    718
    -          self._settings.proxy.username = null;
    
    719
    -          self._settings.proxy.password = null;
    
    720
    -        },
    
    721
    -        get type() {
    
    722
    -          return self._settings.proxy.type;
    
    723
    -        },
    
    724
    -        set type(val) {
    
    725
    -          self._settings.proxy.type = val;
    
    726
    -        },
    
    727
    -        get address() {
    
    728
    -          return self._settings.proxy.address;
    
    729
    -        },
    
    730
    -        set address(val) {
    
    731
    -          self._settings.proxy.address = val;
    
    732
    -        },
    
    733
    -        get port() {
    
    734
    -          return arrayCopy(self._settings.proxy.port);
    
    735
    -        },
    
    736
    -        set port(val) {
    
    737
    -          self._settings.proxy.port = parsePort(val);
    
    738
    -        },
    
    739
    -        get username() {
    
    740
    -          return self._settings.proxy.username;
    
    741
    -        },
    
    742
    -        set username(val) {
    
    743
    -          self._settings.proxy.username = val;
    
    744
    -        },
    
    745
    -        get password() {
    
    746
    -          return self._settings.proxy.password;
    
    747
    -        },
    
    748
    -        set password(val) {
    
    749
    -          self._settings.proxy.password = val;
    
    750
    -        },
    
    751
    -        get uri() {
    
    752
    -          switch (this.type) {
    
    753
    -            case TorProxyType.Socks4:
    
    754
    -              return `socks4a://${this.address}:${this.port}`;
    
    755
    -            case TorProxyType.Socks5:
    
    756
    -              if (this.username) {
    
    757
    -                return `socks5://${this.username}:${this.password}@${this.address}:${this.port}`;
    
    758
    -              }
    
    759
    -              return `socks5://${this.address}:${this.port}`;
    
    760
    -            case TorProxyType.HTTPS:
    
    761
    -              if (this._proxyUsername) {
    
    762
    -                return `http://${this.username}:${this.password}@${this.address}:${this.port}`;
    
    763
    -              }
    
    764
    -              return `http://${this.address}:${this.port}`;
    
    765
    -          }
    
    766
    -          return null;
    
    767
    -        },
    
    768
    -      };
    
    769
    -    },
    
    878
    +      lazy.logger.error("setSettings failed", ex);
    
    879
    +    } finally {
    
    880
    +      this.thawNotifications();
    
    881
    +    }
    
    770 882
     
    
    771
    -    // Firewall
    
    772
    -    get firewall() {
    
    773
    -      return {
    
    774
    -        get enabled() {
    
    775
    -          return self._settings.firewall.enabled;
    
    776
    -        },
    
    777
    -        set enabled(val) {
    
    778
    -          self._settings.firewall.enabled = val;
    
    779
    -          // reset firewall settings
    
    780
    -          self._settings.firewall.allowed_ports = [];
    
    781
    -        },
    
    782
    -        get allowed_ports() {
    
    783
    -          return self._settings.firewall.allowed_ports;
    
    784
    -        },
    
    785
    -        set allowed_ports(val) {
    
    786
    -          self._settings.firewall.allowed_ports = parsePortList(val);
    
    787
    -        },
    
    788
    -      };
    
    789
    -    },
    
    790
    -  };
    
    791
    -  return self;
    
    792
    -})();
    883
    +    lazy.logger.debug("setSettings result", this._settings);
    
    884
    +  },
    
    885
    +
    
    886
    +  // get a copy of all our settings
    
    887
    +  getSettings() {
    
    888
    +    lazy.logger.debug("getSettings()");
    
    889
    +    return structuredClone(this._settings);
    
    890
    +  },
    
    891
    +};