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

Commits:

3 changed files:

Changes:

  • browser/components/torpreferences/content/connectionPane.js
    ... ... @@ -2319,7 +2319,7 @@ const gBridgeSettings = {
    2319 2319
               bridges: {
    
    2320 2320
                 enabled: true,
    
    2321 2321
                 source: TorBridgeSource.BridgeDB,
    
    2322
    -            bridge_strings: result.bridges.join("\n"),
    
    2322
    +            bridge_strings: result.bridges,
    
    2323 2323
               },
    
    2324 2324
             });
    
    2325 2325
           }
    

  • toolkit/modules/DomainFrontedRequests.sys.mjs
    ... ... @@ -130,7 +130,7 @@ class MeekTransport {
    130 130
             TOR_PT_CLIENT_TRANSPORTS: meekTransport,
    
    131 131
           };
    
    132 132
           if (lazy.TorSettings.proxy.enabled) {
    
    133
    -        envAdditions.TOR_PT_PROXY = lazy.TorSettings.proxy.uri;
    
    133
    +        envAdditions.TOR_PT_PROXY = lazy.TorSettings.proxyUri;
    
    134 134
           }
    
    135 135
     
    
    136 136
           const opts = {
    

  • toolkit/modules/TorSettings.sys.mjs
    ... ... @@ -179,10 +179,34 @@ class TorSettingsImpl {
    179 179
           enabled: false,
    
    180 180
         },
    
    181 181
         bridges: {
    
    182
    +      /**
    
    183
    +       * Whether the bridges are enabled or not.
    
    184
    +       *
    
    185
    +       * @type {boolean}
    
    186
    +       */
    
    182 187
           enabled: false,
    
    183 188
           source: TorBridgeSource.Invalid,
    
    189
    +      /**
    
    190
    +       * The lox id is used with the Lox "source", and remains set with the
    
    191
    +       * stored value when other sources are used.
    
    192
    +       *
    
    193
    +       * @type {string}
    
    194
    +       */
    
    184 195
           lox_id: "",
    
    196
    +      /**
    
    197
    +       * The built-in type to use when using the BuiltIn "source", or empty when
    
    198
    +       * using any other source.
    
    199
    +       *
    
    200
    +       * @type {string}
    
    201
    +       */
    
    185 202
           builtin_type: "",
    
    203
    +      /**
    
    204
    +       * The current bridge strings.
    
    205
    +       *
    
    206
    +       * Can only be non-empty if the "source" is not Invalid.
    
    207
    +       *
    
    208
    +       * @type {Array<string>}
    
    209
    +       */
    
    186 210
           bridge_strings: [],
    
    187 211
         },
    
    188 212
         proxy: {
    
    ... ... @@ -206,15 +230,6 @@ class TorSettingsImpl {
    206 230
        */
    
    207 231
       #temporaryBridgeSettings = null;
    
    208 232
     
    
    209
    -  /**
    
    210
    -   * Accumulated errors from trying to set settings.
    
    211
    -   *
    
    212
    -   * Only added to if not null.
    
    213
    -   *
    
    214
    -   * @type {Array<Error>?}
    
    215
    -   */
    
    216
    -  #settingErrors = null;
    
    217
    -
    
    218 233
       /**
    
    219 234
        * The recommended pluggable transport.
    
    220 235
        *
    
    ... ... @@ -245,13 +260,6 @@ class TorSettingsImpl {
    245 260
        * @type {boolean}
    
    246 261
        */
    
    247 262
       #initialized = false;
    
    248
    -  /**
    
    249
    -   * During some phases of the initialization, allow calling setters and
    
    250
    -   * getters without throwing errors.
    
    251
    -   *
    
    252
    -   * @type {boolean}
    
    253
    -   */
    
    254
    -  #allowUninitialized = false;
    
    255 263
     
    
    256 264
       constructor() {
    
    257 265
         this.initializedPromise = new Promise((resolve, reject) => {
    
    ... ... @@ -259,347 +267,57 @@ class TorSettingsImpl {
    259 267
           this.#initFailed = reject;
    
    260 268
         });
    
    261 269
     
    
    262
    -    this.#addProperties("quickstart", {
    
    263
    -      enabled: {},
    
    264
    -    });
    
    265
    -    this.#addProperties("bridges", {
    
    266
    -      /**
    
    267
    -       * Whether the bridges are enabled or not.
    
    268
    -       *
    
    269
    -       * @type {boolean}
    
    270
    -       */
    
    271
    -      enabled: {},
    
    272
    -      /**
    
    273
    -       * The current bridge source.
    
    274
    -       *
    
    275
    -       * @type {integer}
    
    276
    -       */
    
    277
    -      source: {
    
    278
    -        transform: (val, addError) => {
    
    279
    -          if (Object.values(TorBridgeSource).includes(val)) {
    
    280
    -            return val;
    
    281
    -          }
    
    282
    -          addError(`Not a valid bridge source: "${val}"`);
    
    283
    -          return TorBridgeSource.Invalid;
    
    284
    -        },
    
    285
    -      },
    
    286
    -      /**
    
    287
    -       * The current bridge strings.
    
    288
    -       *
    
    289
    -       * Can only be non-empty if the "source" is not Invalid.
    
    290
    -       *
    
    291
    -       * @type {Array<string>}
    
    292
    -       */
    
    293
    -      bridge_strings: {
    
    294
    -        transform: val => {
    
    295
    -          if (Array.isArray(val)) {
    
    296
    -            return [...val];
    
    297
    -          }
    
    298
    -          // Split the bridge strings, discarding empty.
    
    299
    -          return splitBridgeLines(val).filter(val => val);
    
    300
    -        },
    
    301
    -        copy: val => [...val],
    
    302
    -        equal: (val1, val2) => this.#arrayEqual(val1, val2),
    
    303
    -      },
    
    304
    -      /**
    
    305
    -       * The built-in type to use when using the BuiltIn "source", or empty when
    
    306
    -       * using any other source.
    
    307
    -       *
    
    308
    -       * @type {string}
    
    309
    -       */
    
    310
    -      builtin_type: {
    
    311
    -        callback: (val, addError) => {
    
    312
    -          if (!val) {
    
    313
    -            return;
    
    314
    -          }
    
    315
    -          const bridgeStrings = this.getBuiltinBridges(val);
    
    316
    -          if (bridgeStrings.length) {
    
    317
    -            this.bridges.bridge_strings = bridgeStrings;
    
    318
    -            return;
    
    319
    -          }
    
    320
    -
    
    321
    -          addError(`No built-in ${val} bridges found`);
    
    322
    -          // Set as invalid, which will make the builtin_type "" and set the
    
    323
    -          // bridge_strings to be empty at the next #cleanupSettings.
    
    324
    -          this.bridges.source = TorBridgeSource.Invalid;
    
    325
    -        },
    
    326
    -      },
    
    327
    -      /**
    
    328
    -       * The lox id is used with the Lox "source", and remains set with the stored value when
    
    329
    -       * other sources are used.
    
    330
    -       *
    
    331
    -       * @type {string}
    
    332
    -       */
    
    333
    -      lox_id: {
    
    334
    -        callback: (val, addError) => {
    
    335
    -          if (!val) {
    
    336
    -            return;
    
    337
    -          }
    
    338
    -          let bridgeStrings;
    
    339
    -          try {
    
    340
    -            bridgeStrings = lazy.Lox.getBridges(val);
    
    341
    -          } catch (error) {
    
    342
    -            addError(`No bridges for lox_id ${val}: ${error?.message}`);
    
    343
    -            // Set as invalid, which will make the builtin_type "" and set the
    
    344
    -            // bridge_strings to be empty at the next #cleanupSettings.
    
    345
    -            this.bridges.source = TorBridgeSource.Invalid;
    
    346
    -            return;
    
    347
    -          }
    
    348
    -          this.bridges.bridge_strings = bridgeStrings;
    
    349
    -        },
    
    350
    -      },
    
    351
    -    });
    
    352
    -    this.#addProperties("proxy", {
    
    353
    -      enabled: {},
    
    354
    -      type: {
    
    355
    -        transform: (val, addError) => {
    
    356
    -          if (Object.values(TorProxyType).includes(val)) {
    
    357
    -            return val;
    
    358
    -          }
    
    359
    -          addError(`Not a valid proxy type: "${val}"`);
    
    360
    -          return TorProxyType.Invalid;
    
    361
    -        },
    
    362
    -      },
    
    363
    -      address: {},
    
    364
    -      port: {
    
    365
    -        transform: (val, addError) => {
    
    366
    -          if (val === 0) {
    
    367
    -            // This is a valid value that "unsets" the port.
    
    368
    -            // Keep this value without giving a warning.
    
    369
    -            // NOTE: In contrast, "0" is not valid.
    
    370
    -            return 0;
    
    371
    -          }
    
    372
    -          // Unset to 0 if invalid null is returned.
    
    373
    -          return this.#parsePort(val, false, addError) ?? 0;
    
    374
    -        },
    
    375
    -      },
    
    376
    -      username: {},
    
    377
    -      password: {},
    
    378
    -      uri: {
    
    379
    -        getter: () => {
    
    380
    -          const { type, address, port, username, password } = this.proxy;
    
    381
    -          switch (type) {
    
    382
    -            case TorProxyType.Socks4:
    
    383
    -              return `socks4a://${address}:${port}`;
    
    384
    -            case TorProxyType.Socks5:
    
    385
    -              if (username) {
    
    386
    -                return `socks5://${username}:${password}@${address}:${port}`;
    
    387
    -              }
    
    388
    -              return `socks5://${address}:${port}`;
    
    389
    -            case TorProxyType.HTTPS:
    
    390
    -              if (username) {
    
    391
    -                return `http://${username}:${password}@${address}:${port}`;
    
    392
    -              }
    
    393
    -              return `http://${address}:${port}`;
    
    394
    -          }
    
    395
    -          return null;
    
    396
    -        },
    
    397
    -      },
    
    398
    -    });
    
    399
    -    this.#addProperties("firewall", {
    
    400
    -      enabled: {},
    
    401
    -      allowed_ports: {
    
    402
    -        transform: (val, addError) => {
    
    403
    -          if (!Array.isArray(val)) {
    
    404
    -            val = val === "" ? [] : val.split(",");
    
    405
    -          }
    
    406
    -          // parse and remove duplicates
    
    407
    -          const portSet = new Set(
    
    408
    -            val.map(p => this.#parsePort(p, true, addError))
    
    409
    -          );
    
    410
    -          // parsePort returns null for failed parses, so remove it.
    
    411
    -          portSet.delete(null);
    
    412
    -          return [...portSet];
    
    413
    -        },
    
    414
    -        copy: val => [...val],
    
    415
    -        equal: (val1, val2) => this.#arrayEqual(val1, val2),
    
    416
    -      },
    
    417
    -    });
    
    418
    -  }
    
    419
    -
    
    420
    -  /**
    
    421
    -   * Clean the setting values after making some changes, so that the values do
    
    422
    -   * not contradict each other.
    
    423
    -   */
    
    424
    -  #cleanupSettings() {
    
    425
    -    this.#freezeNotifications();
    
    426
    -    try {
    
    427
    -      if (this.bridges.source === TorBridgeSource.Invalid) {
    
    428
    -        this.bridges.enabled = false;
    
    429
    -        this.bridges.bridge_strings = [];
    
    270
    +    // Add some read-only getters for the #settings object.
    
    271
    +    // E.g. TorSetting.#settings.bridges.source is exposed publicly as
    
    272
    +    // TorSettings.bridges.source.
    
    273
    +    for (const groupname in this.#settings) {
    
    274
    +      const publicGroup = {};
    
    275
    +      for (const name in this.#settings[groupname]) {
    
    276
    +        // Public group only has a getter for the property.
    
    277
    +        Object.defineProperty(publicGroup, name, {
    
    278
    +          get: () => {
    
    279
    +            this.#checkIfInitialized();
    
    280
    +            return structuredClone(this.#settings[groupname][name]);
    
    281
    +          },
    
    282
    +          set: () => {
    
    283
    +            throw new Error(
    
    284
    +              `TorSettings.${groupname}.${name} cannot be set directly`
    
    285
    +            );
    
    286
    +          },
    
    287
    +        });
    
    430 288
           }
    
    431
    -      if (!this.bridges.bridge_strings.length) {
    
    432
    -        this.bridges.enabled = false;
    
    433
    -        this.bridges.source = TorBridgeSource.Invalid;
    
    434
    -      }
    
    435
    -      if (this.bridges.source !== TorBridgeSource.BuiltIn) {
    
    436
    -        this.bridges.builtin_type = "";
    
    437
    -      }
    
    438
    -      if (this.bridges.source !== TorBridgeSource.Lox) {
    
    439
    -        this.bridges.lox_id = "";
    
    440
    -      }
    
    441
    -      if (!this.proxy.enabled) {
    
    442
    -        this.proxy.type = TorProxyType.Invalid;
    
    443
    -        this.proxy.address = "";
    
    444
    -        this.proxy.port = 0;
    
    445
    -        this.proxy.username = "";
    
    446
    -        this.proxy.password = "";
    
    447
    -      }
    
    448
    -      if (!this.firewall.enabled) {
    
    449
    -        this.firewall.allowed_ports = [];
    
    450
    -      }
    
    451
    -    } finally {
    
    452
    -      this.#thawNotifications();
    
    289
    +      // The group object itself should not be writable.
    
    290
    +      Object.preventExtensions(publicGroup);
    
    291
    +      Object.defineProperty(this, groupname, {
    
    292
    +        writable: false,
    
    293
    +        value: publicGroup,
    
    294
    +      });
    
    453 295
         }
    
    454 296
       }
    
    455 297
     
    
    456 298
       /**
    
    457
    -   * The current number of freezes applied to the notifications.
    
    458
    -   *
    
    459
    -   * @type {integer}
    
    460
    -   */
    
    461
    -  #freezeNotificationsCount = 0;
    
    462
    -  /**
    
    463
    -   * The queue for settings that have changed. To be broadcast in the
    
    464
    -   * notification when not frozen.
    
    465
    -   *
    
    466
    -   * @type {Set<string>}
    
    467
    -   */
    
    468
    -  #notificationQueue = new Set();
    
    469
    -  /**
    
    470
    -   * Send a notification if we have any queued and we are not frozen.
    
    471
    -   */
    
    472
    -  #tryNotification() {
    
    473
    -    if (this.#freezeNotificationsCount || !this.#notificationQueue.size) {
    
    474
    -      return;
    
    475
    -    }
    
    476
    -    Services.obs.notifyObservers(
    
    477
    -      { changes: [...this.#notificationQueue] },
    
    478
    -      TorSettingsTopics.SettingsChanged
    
    479
    -    );
    
    480
    -    this.#notificationQueue.clear();
    
    481
    -  }
    
    482
    -  /**
    
    483
    -   * Pause notifications for changes in setting values. This is useful if you
    
    484
    -   * need to make batch changes to settings.
    
    485
    -   *
    
    486
    -   * This should always be paired with a call to thawNotifications once
    
    487
    -   * notifications should be released. Usually you should wrap whatever
    
    488
    -   * changes you make with a `try` block and call thawNotifications in the
    
    489
    -   * `finally` block.
    
    490
    -   */
    
    491
    -  #freezeNotifications() {
    
    492
    -    this.#freezeNotificationsCount++;
    
    493
    -  }
    
    494
    -  /**
    
    495
    -   * Release the hold on notifications so they may be sent out.
    
    496
    -   *
    
    497
    -   * Note, if some other method has also frozen the notifications, this will
    
    498
    -   * only release them once it has also called this method.
    
    499
    -   */
    
    500
    -  #thawNotifications() {
    
    501
    -    this.#freezeNotificationsCount--;
    
    502
    -    this.#tryNotification();
    
    503
    -  }
    
    504
    -  /**
    
    505
    -   * @typedef {object} TorSettingProperty
    
    299
    +   * The proxy URI for the current settings, or `null` if no proxy is
    
    300
    +   * configured.
    
    506 301
        *
    
    507
    -   * @property {function} [getter] - A getter for the property. If this is
    
    508
    -   *   given, the property cannot be set.
    
    509
    -   * @property {function} [transform] - Called in the setter for the property,
    
    510
    -   *   with the new value given. Should transform the given value into the
    
    511
    -   *   right type.
    
    512
    -   * @property {function} [equal] - Test whether two values for the property
    
    513
    -   *   are considered equal. Otherwise uses `===`.
    
    514
    -   * @property {function} [callback] - Called whenever the property value
    
    515
    -   *   changes, with the new value given. Should be used to trigger any other
    
    516
    -   *   required changes for the new value.
    
    517
    -   * @property {function} [copy] - Called whenever the property is read, with
    
    518
    -   *   the stored value given. Should return a copy of the value. Otherwise
    
    519
    -   *   returns the stored value.
    
    302
    +   * @type {?string}
    
    520 303
        */
    
    521
    -  /**
    
    522
    -   * Add properties to the TorSettings instance, to be read or set.
    
    523
    -   *
    
    524
    -   * @param {string} groupname - The name of the setting group. The given
    
    525
    -   *   settings will be accessible from the TorSettings property of the same
    
    526
    -   *   name.
    
    527
    -   * @param {object.<string, TorSettingProperty>} propParams - An object that
    
    528
    -   *   defines the settings to add to this group. The object property names
    
    529
    -   *   will be mapped to properties of TorSettings under the given groupname
    
    530
    -   *   property. Details about the setting should be described in the
    
    531
    -   *   TorSettingProperty property value.
    
    532
    -   */
    
    533
    -  #addProperties(groupname, propParams) {
    
    534
    -    // Create a new object to hold all these settings.
    
    535
    -    const group = {};
    
    536
    -    for (const name in propParams) {
    
    537
    -      const { getter, transform, callback, copy, equal } = propParams[name];
    
    538
    -      // Method for adding setting errors.
    
    539
    -      const addError = message => {
    
    540
    -        message = `TorSettings.${groupname}.${name}: ${message}`;
    
    541
    -        lazy.logger.error(message);
    
    542
    -        // Only add to #settingErrors if it is not null.
    
    543
    -        this.#settingErrors?.push(message);
    
    544
    -      };
    
    545
    -      Object.defineProperty(group, name, {
    
    546
    -        get: getter
    
    547
    -          ? () => {
    
    548
    -              // Allow getting in loadFromPrefs before we are initialized.
    
    549
    -              if (!this.#allowUninitialized) {
    
    550
    -                this.#checkIfInitialized();
    
    551
    -              }
    
    552
    -              return getter();
    
    553
    -            }
    
    554
    -          : () => {
    
    555
    -              // Allow getting in loadFromPrefs before we are initialized.
    
    556
    -              if (!this.#allowUninitialized) {
    
    557
    -                this.#checkIfInitialized();
    
    558
    -              }
    
    559
    -              let val = this.#settings[groupname][name];
    
    560
    -              if (copy) {
    
    561
    -                val = copy(val);
    
    562
    -              }
    
    563
    -              // Assume string or number value.
    
    564
    -              return val;
    
    565
    -            },
    
    566
    -        set: getter
    
    567
    -          ? undefined
    
    568
    -          : val => {
    
    569
    -              // Allow setting in loadFromPrefs before we are initialized.
    
    570
    -              if (!this.#allowUninitialized) {
    
    571
    -                this.#checkIfInitialized();
    
    572
    -              }
    
    573
    -              const prevVal = this.#settings[groupname][name];
    
    574
    -              this.#freezeNotifications();
    
    575
    -              try {
    
    576
    -                if (transform) {
    
    577
    -                  val = transform(val, addError);
    
    578
    -                }
    
    579
    -                const isEqual = equal ? equal(val, prevVal) : val === prevVal;
    
    580
    -                if (!isEqual) {
    
    581
    -                  // Set before the callback.
    
    582
    -                  this.#settings[groupname][name] = val;
    
    583
    -                  this.#notificationQueue.add(`${groupname}.${name}`);
    
    584
    -
    
    585
    -                  if (callback) {
    
    586
    -                    callback(val, addError);
    
    587
    -                  }
    
    588
    -                }
    
    589
    -              } catch (e) {
    
    590
    -                addError(e.message);
    
    591
    -              } finally {
    
    592
    -                this.#thawNotifications();
    
    593
    -              }
    
    594
    -            },
    
    595
    -      });
    
    304
    +  get proxyUri() {
    
    305
    +    const { type, address, port, username, password } = this.#settings.proxy;
    
    306
    +    switch (type) {
    
    307
    +      case TorProxyType.Socks4:
    
    308
    +        return `socks4a://${address}:${port}`;
    
    309
    +      case TorProxyType.Socks5:
    
    310
    +        if (username) {
    
    311
    +          return `socks5://${username}:${password}@${address}:${port}`;
    
    312
    +        }
    
    313
    +        return `socks5://${address}:${port}`;
    
    314
    +      case TorProxyType.HTTPS:
    
    315
    +        if (username) {
    
    316
    +          return `http://${username}:${password}@${address}:${port}`;
    
    317
    +        }
    
    318
    +        return `http://${address}:${port}`;
    
    596 319
         }
    
    597
    -    // The group object itself should not be writable.
    
    598
    -    Object.preventExtensions(group);
    
    599
    -    Object.defineProperty(this, groupname, {
    
    600
    -      writable: false,
    
    601
    -      value: group,
    
    602
    -    });
    
    320
    +    return null;
    
    603 321
       }
    
    604 322
     
    
    605 323
       /**
    
    ... ... @@ -614,12 +332,11 @@ class TorSettingsImpl {
    614 332
        * @param {string|integer} val - The value to parse.
    
    615 333
        * @param {boolean} trim - Whether a string value can be stripped of
    
    616 334
        *   whitespace before parsing.
    
    617
    -   * @param {function} addError - Callback to add error messages to.
    
    618 335
        *
    
    619 336
        * @return {integer?} - The port number, or null if the given value was not
    
    620 337
        *   valid.
    
    621 338
        */
    
    622
    -  #parsePort(val, trim, addError) {
    
    339
    +  #parsePort(val, trim) {
    
    623 340
         if (typeof val === "string") {
    
    624 341
           if (trim) {
    
    625 342
             val = val.trim();
    
    ... ... @@ -628,13 +345,11 @@ class TorSettingsImpl {
    628 345
           if (this.#portRegex.test(val)) {
    
    629 346
             val = Number.parseInt(val, 10);
    
    630 347
           } else {
    
    631
    -        addError(`Invalid port string "${val}"`);
    
    632
    -        return null;
    
    348
    +        throw new Error(`Invalid port string "${val}"`);
    
    633 349
           }
    
    634 350
         }
    
    635 351
         if (!Number.isInteger(val) || val < 1 || val > 65535) {
    
    636
    -      addError(`Port out of range: ${val}`);
    
    637
    -      return null;
    
    352
    +      throw new Error(`Port out of range: ${val}`);
    
    638 353
         }
    
    639 354
         return val;
    
    640 355
       }
    
    ... ... @@ -659,13 +374,8 @@ class TorSettingsImpl {
    659 374
        * @param {string} pt The pluggable transport to return the lines for
    
    660 375
        * @returns {string[]} The bridge lines in random order
    
    661 376
        */
    
    662
    -  getBuiltinBridges(pt) {
    
    663
    -    if (!this.#allowUninitialized) {
    
    664
    -      this.#checkIfInitialized();
    
    665
    -    }
    
    666
    -    // Shuffle so that Tor Browser users do not all try the built-in bridges in
    
    667
    -    // the same order.
    
    668
    -    return arrayShuffle(this.#builtinBridges[pt] ?? []);
    
    377
    +  #getBuiltinBridges(pt) {
    
    378
    +    return this.#builtinBridges[pt] ?? [];
    
    669 379
       }
    
    670 380
     
    
    671 381
       /**
    
    ... ... @@ -698,6 +408,13 @@ class TorSettingsImpl {
    698 408
           lazy.logger.debug("Loaded pt_config.json", config);
    
    699 409
           this.#recommendedPT = config.recommendedDefault;
    
    700 410
           this.#builtinBridges = config.bridges;
    
    411
    +      for (const type in this.#builtinBridges) {
    
    412
    +        // Shuffle so that Tor Browser users do not all try the built-in bridges
    
    413
    +        // in the same order.
    
    414
    +        // Only do this once per session. In particular, we don't re-shuffle if
    
    415
    +        // changeSettings is called with the same bridges.builtin_type value.
    
    416
    +        this.#builtinBridges[type] = arrayShuffle(this.#builtinBridges[type]);
    
    417
    +      }
    
    701 418
         } catch (e) {
    
    702 419
           lazy.logger.error("Could not load the built-in PT config.", e);
    
    703 420
         }
    
    ... ... @@ -716,18 +433,9 @@ class TorSettingsImpl {
    716 433
           lazy.TorLauncherUtil.shouldStartAndOwnTor &&
    
    717 434
           Services.prefs.getBoolPref(TorSettingsPrefs.enabled, false)
    
    718 435
         ) {
    
    719
    -      // Do not want notifications for initially loaded prefs.
    
    720
    -      this.#freezeNotifications();
    
    721
    -      try {
    
    722
    -        this.#allowUninitialized = true;
    
    723
    -        this.#loadFromPrefs();
    
    724
    -        // We do not pass on the loaded settings to the TorProvider yet. Instead
    
    725
    -        // TorProvider will ask for these once it has initialised.
    
    726
    -      } finally {
    
    727
    -        this.#allowUninitialized = false;
    
    728
    -        this.#notificationQueue.clear();
    
    729
    -        this.#thawNotifications();
    
    730
    -      }
    
    436
    +      this.#loadFromPrefs();
    
    437
    +      // We do not pass on the loaded settings to the TorProvider yet. Instead
    
    438
    +      // TorProvider will ask for these once it has initialised.
    
    731 439
         }
    
    732 440
     
    
    733 441
         Services.obs.addObserver(this, lazy.LoxTopics.UpdateBridges);
    
    ... ... @@ -746,16 +454,19 @@ class TorSettingsImpl {
    746 454
       observe(subject, topic) {
    
    747 455
         switch (topic) {
    
    748 456
           case lazy.LoxTopics.UpdateBridges:
    
    749
    -        if (this.bridges.lox_id) {
    
    750
    -          // Fetch the newest bridges.
    
    751
    -          this.bridges.bridge_strings = lazy.Lox.getBridges(
    
    752
    -            this.bridges.lox_id
    
    753
    -          );
    
    754
    -          // No need to save to prefs since bridge_strings is not stored for Lox
    
    755
    -          // source. But we do pass on the changes to TorProvider.
    
    457
    +        if (
    
    458
    +          this.#settings.bridges.lox_id &&
    
    459
    +          this.#settings.bridges.source === TorBridgeSource.Lox
    
    460
    +        ) {
    
    461
    +          // Re-trigger the call to lazy.Lox.getBridges.
    
    756 462
               // FIXME: This can compete with TorConnect to reach TorProvider.
    
    757 463
               // tor-browser#42316
    
    758
    -          this.#applySettings();
    
    464
    +          this.changeSettings({
    
    465
    +            bridges: {
    
    466
    +              source: TorBridgeSource.Lox,
    
    467
    +              lox_id: this.#settings.bridges.lox_id,
    
    468
    +            },
    
    469
    +          });
    
    759 470
             }
    
    760 471
             break;
    
    761 472
         }
    
    ... ... @@ -790,23 +501,24 @@ class TorSettingsImpl {
    790 501
         lazy.logger.debug("loadFromPrefs()");
    
    791 502
     
    
    792 503
         /* Quickstart */
    
    793
    -    this.quickstart.enabled = Services.prefs.getBoolPref(
    
    504
    +    this.#settings.quickstart.enabled = Services.prefs.getBoolPref(
    
    794 505
           TorSettingsPrefs.quickstart.enabled,
    
    795 506
           false
    
    796 507
         );
    
    797 508
         /* Bridges */
    
    798
    -    this.bridges.enabled = Services.prefs.getBoolPref(
    
    509
    +    const bridges = {};
    
    510
    +    bridges.enabled = Services.prefs.getBoolPref(
    
    799 511
           TorSettingsPrefs.bridges.enabled,
    
    800 512
           false
    
    801 513
         );
    
    802
    -    this.bridges.source = Services.prefs.getIntPref(
    
    514
    +    bridges.source = Services.prefs.getIntPref(
    
    803 515
           TorSettingsPrefs.bridges.source,
    
    804 516
           TorBridgeSource.Invalid
    
    805 517
         );
    
    806
    -    switch (this.bridges.source) {
    
    518
    +    switch (bridges.source) {
    
    807 519
           case TorBridgeSource.BridgeDB:
    
    808 520
           case TorBridgeSource.UserProvided:
    
    809
    -        this.bridges.bridge_strings = Services.prefs
    
    521
    +        bridges.bridge_strings = Services.prefs
    
    810 522
               .getBranch(TorSettingsPrefs.bridges.bridge_strings)
    
    811 523
               .getChildList("")
    
    812 524
               .map(pref =>
    
    ... ... @@ -817,60 +529,79 @@ class TorSettingsImpl {
    817 529
             break;
    
    818 530
           case TorBridgeSource.BuiltIn:
    
    819 531
             // bridge_strings is set via builtin_type.
    
    820
    -        this.bridges.builtin_type = Services.prefs.getStringPref(
    
    532
    +        bridges.builtin_type = Services.prefs.getStringPref(
    
    821 533
               TorSettingsPrefs.bridges.builtin_type,
    
    822 534
               ""
    
    823 535
             );
    
    824 536
             break;
    
    825 537
           case TorBridgeSource.Lox:
    
    826 538
             // bridge_strings is set via lox id.
    
    827
    -        this.bridges.lox_id = Services.prefs.getStringPref(
    
    539
    +        bridges.lox_id = Services.prefs.getStringPref(
    
    828 540
               TorSettingsPrefs.bridges.lox_id,
    
    829 541
               ""
    
    830 542
             );
    
    831 543
             break;
    
    832 544
         }
    
    545
    +    try {
    
    546
    +      this.#fixupBridgeSettings(bridges);
    
    547
    +      this.#settings.bridges = bridges;
    
    548
    +    } catch (error) {
    
    549
    +      lazy.logger.error("Loaded bridge preferences failed", error);
    
    550
    +      // Keep the default #settings.bridges.
    
    551
    +    }
    
    552
    +
    
    833 553
         /* Proxy */
    
    834
    -    this.proxy.enabled = Services.prefs.getBoolPref(
    
    554
    +    const proxy = {};
    
    555
    +    proxy.enabled = Services.prefs.getBoolPref(
    
    835 556
           TorSettingsPrefs.proxy.enabled,
    
    836 557
           false
    
    837 558
         );
    
    838
    -    if (this.proxy.enabled) {
    
    839
    -      this.proxy.type = Services.prefs.getIntPref(
    
    559
    +    if (proxy.enabled) {
    
    560
    +      proxy.type = Services.prefs.getIntPref(
    
    840 561
             TorSettingsPrefs.proxy.type,
    
    841 562
             TorProxyType.Invalid
    
    842 563
           );
    
    843
    -      this.proxy.address = Services.prefs.getStringPref(
    
    564
    +      proxy.address = Services.prefs.getStringPref(
    
    844 565
             TorSettingsPrefs.proxy.address,
    
    845 566
             ""
    
    846 567
           );
    
    847
    -      this.proxy.port = Services.prefs.getIntPref(
    
    848
    -        TorSettingsPrefs.proxy.port,
    
    849
    -        0
    
    850
    -      );
    
    851
    -      this.proxy.username = Services.prefs.getStringPref(
    
    568
    +      proxy.port = Services.prefs.getIntPref(TorSettingsPrefs.proxy.port, 0);
    
    569
    +      proxy.username = Services.prefs.getStringPref(
    
    852 570
             TorSettingsPrefs.proxy.username,
    
    853 571
             ""
    
    854 572
           );
    
    855
    -      this.proxy.password = Services.prefs.getStringPref(
    
    573
    +      proxy.password = Services.prefs.getStringPref(
    
    856 574
             TorSettingsPrefs.proxy.password,
    
    857 575
             ""
    
    858 576
           );
    
    859 577
         }
    
    578
    +    try {
    
    579
    +      this.#fixupProxySettings(proxy);
    
    580
    +      this.#settings.proxy = proxy;
    
    581
    +    } catch (error) {
    
    582
    +      lazy.logger.error("Loaded proxy preferences failed", error);
    
    583
    +      // Keep the default #settings.proxy.
    
    584
    +    }
    
    860 585
     
    
    861 586
         /* Firewall */
    
    862
    -    this.firewall.enabled = Services.prefs.getBoolPref(
    
    587
    +    const firewall = {};
    
    588
    +    firewall.enabled = Services.prefs.getBoolPref(
    
    863 589
           TorSettingsPrefs.firewall.enabled,
    
    864 590
           false
    
    865 591
         );
    
    866
    -    if (this.firewall.enabled) {
    
    867
    -      this.firewall.allowed_ports = Services.prefs.getStringPref(
    
    592
    +    if (firewall.enabled) {
    
    593
    +      firewall.allowed_ports = Services.prefs.getStringPref(
    
    868 594
             TorSettingsPrefs.firewall.allowed_ports,
    
    869 595
             ""
    
    870 596
           );
    
    871 597
         }
    
    872
    -
    
    873
    -    this.#cleanupSettings();
    
    598
    +    try {
    
    599
    +      this.#fixupFirewallSettings(firewall);
    
    600
    +      this.#settings.firewall = firewall;
    
    601
    +    } catch (error) {
    
    602
    +      lazy.logger.error("Loaded firewall preferences failed", error);
    
    603
    +      // Keep the default #settings.firewall.
    
    604
    +    }
    
    874 605
       }
    
    875 606
     
    
    876 607
       /**
    
    ... ... @@ -880,29 +611,28 @@ class TorSettingsImpl {
    880 611
         lazy.logger.debug("saveToPrefs()");
    
    881 612
     
    
    882 613
         this.#checkIfInitialized();
    
    883
    -    this.#cleanupSettings();
    
    884 614
     
    
    885 615
         /* Quickstart */
    
    886 616
         Services.prefs.setBoolPref(
    
    887 617
           TorSettingsPrefs.quickstart.enabled,
    
    888
    -      this.quickstart.enabled
    
    618
    +      this.#settings.quickstart.enabled
    
    889 619
         );
    
    890 620
         /* Bridges */
    
    891 621
         Services.prefs.setBoolPref(
    
    892 622
           TorSettingsPrefs.bridges.enabled,
    
    893
    -      this.bridges.enabled
    
    623
    +      this.#settings.bridges.enabled
    
    894 624
         );
    
    895 625
         Services.prefs.setIntPref(
    
    896 626
           TorSettingsPrefs.bridges.source,
    
    897
    -      this.bridges.source
    
    627
    +      this.#settings.bridges.source
    
    898 628
         );
    
    899 629
         Services.prefs.setStringPref(
    
    900 630
           TorSettingsPrefs.bridges.builtin_type,
    
    901
    -      this.bridges.builtin_type
    
    631
    +      this.#settings.bridges.builtin_type
    
    902 632
         );
    
    903 633
         Services.prefs.setStringPref(
    
    904 634
           TorSettingsPrefs.bridges.lox_id,
    
    905
    -      this.bridges.lox_id
    
    635
    +      this.#settings.bridges.lox_id
    
    906 636
         );
    
    907 637
         // erase existing bridge strings
    
    908 638
         const bridgeBranchPrefs = Services.prefs
    
    ... ... @@ -915,10 +645,10 @@ class TorSettingsImpl {
    915 645
         });
    
    916 646
         // write new ones
    
    917 647
         if (
    
    918
    -      this.bridges.source !== TorBridgeSource.Lox &&
    
    919
    -      this.bridges.source !== TorBridgeSource.BuiltIn
    
    648
    +      this.#settings.bridges.source !== TorBridgeSource.Lox &&
    
    649
    +      this.#settings.bridges.source !== TorBridgeSource.BuiltIn
    
    920 650
         ) {
    
    921
    -      this.bridges.bridge_strings.forEach((string, index) => {
    
    651
    +      this.#settings.bridges.bridge_strings.forEach((string, index) => {
    
    922 652
             Services.prefs.setStringPref(
    
    923 653
               `${TorSettingsPrefs.bridges.bridge_strings}.${index}`,
    
    924 654
               string
    
    ... ... @@ -928,22 +658,28 @@ class TorSettingsImpl {
    928 658
         /* Proxy */
    
    929 659
         Services.prefs.setBoolPref(
    
    930 660
           TorSettingsPrefs.proxy.enabled,
    
    931
    -      this.proxy.enabled
    
    661
    +      this.#settings.proxy.enabled
    
    932 662
         );
    
    933
    -    if (this.proxy.enabled) {
    
    934
    -      Services.prefs.setIntPref(TorSettingsPrefs.proxy.type, this.proxy.type);
    
    663
    +    if (this.#settings.proxy.enabled) {
    
    664
    +      Services.prefs.setIntPref(
    
    665
    +        TorSettingsPrefs.proxy.type,
    
    666
    +        this.#settings.proxy.type
    
    667
    +      );
    
    935 668
           Services.prefs.setStringPref(
    
    936 669
             TorSettingsPrefs.proxy.address,
    
    937
    -        this.proxy.address
    
    670
    +        this.#settings.proxy.address
    
    671
    +      );
    
    672
    +      Services.prefs.setIntPref(
    
    673
    +        TorSettingsPrefs.proxy.port,
    
    674
    +        this.#settings.proxy.port
    
    938 675
           );
    
    939
    -      Services.prefs.setIntPref(TorSettingsPrefs.proxy.port, this.proxy.port);
    
    940 676
           Services.prefs.setStringPref(
    
    941 677
             TorSettingsPrefs.proxy.username,
    
    942
    -        this.proxy.username
    
    678
    +        this.#settings.proxy.username
    
    943 679
           );
    
    944 680
           Services.prefs.setStringPref(
    
    945 681
             TorSettingsPrefs.proxy.password,
    
    946
    -        this.proxy.password
    
    682
    +        this.#settings.proxy.password
    
    947 683
           );
    
    948 684
         } else {
    
    949 685
           Services.prefs.clearUserPref(TorSettingsPrefs.proxy.type);
    
    ... ... @@ -955,12 +691,12 @@ class TorSettingsImpl {
    955 691
         /* Firewall */
    
    956 692
         Services.prefs.setBoolPref(
    
    957 693
           TorSettingsPrefs.firewall.enabled,
    
    958
    -      this.firewall.enabled
    
    694
    +      this.#settings.firewall.enabled
    
    959 695
         );
    
    960
    -    if (this.firewall.enabled) {
    
    696
    +    if (this.#settings.firewall.enabled) {
    
    961 697
           Services.prefs.setStringPref(
    
    962 698
             TorSettingsPrefs.firewall.allowed_ports,
    
    963
    -        this.firewall.allowed_ports.join(",")
    
    699
    +        this.#settings.firewall.allowed_ports.join(",")
    
    964 700
           );
    
    965 701
         } else {
    
    966 702
           Services.prefs.clearUserPref(TorSettingsPrefs.firewall.allowed_ports);
    
    ... ... @@ -977,11 +713,135 @@ class TorSettingsImpl {
    977 713
        * frontend consumers.
    
    978 714
        */
    
    979 715
       async #applySettings() {
    
    980
    -    this.#checkIfInitialized();
    
    981 716
         const provider = await lazy.TorProviderBuilder.build();
    
    982 717
         await provider.writeSettings();
    
    983 718
       }
    
    984 719
     
    
    720
    +  /**
    
    721
    +   * Fixup the given bridges settings to fill in details, establish the correct
    
    722
    +   * types and clean up.
    
    723
    +   *
    
    724
    +   * May throw if there is an error in the given values.
    
    725
    +   *
    
    726
    +   * @param {Object} bridges - The bridges settings to fix up.
    
    727
    +   */
    
    728
    +  #fixupBridgeSettings(bridges) {
    
    729
    +    if (!Object.values(TorBridgeSource).includes(bridges.source)) {
    
    730
    +      throw new Error(`Not a valid bridge source: "${bridges.source}"`);
    
    731
    +    }
    
    732
    +
    
    733
    +    if ("enabled" in bridges) {
    
    734
    +      bridges.enabled = Boolean(bridges.enabled);
    
    735
    +    }
    
    736
    +
    
    737
    +    // Set bridge_strings
    
    738
    +    switch (bridges.source) {
    
    739
    +      case TorBridgeSource.UserProvided:
    
    740
    +      case TorBridgeSource.BridgeDB:
    
    741
    +        // Only accept an Array for UserProvided and BridgeDB bridge_strings.
    
    742
    +        break;
    
    743
    +      case TorBridgeSource.BuiltIn:
    
    744
    +        bridges.builtin_type = String(bridges.builtin_type);
    
    745
    +        bridges.bridge_strings = this.#getBuiltinBridges(bridges.builtin_type);
    
    746
    +        break;
    
    747
    +      case TorBridgeSource.Lox:
    
    748
    +        bridges.lox_id = String(bridges.lox_id);
    
    749
    +        bridges.bridge_strings = lazy.Lox.getBridges(bridges.lox_id);
    
    750
    +        break;
    
    751
    +      case TorBridgeSource.Invalid:
    
    752
    +        bridges.bridge_strings = [];
    
    753
    +        break;
    
    754
    +    }
    
    755
    +
    
    756
    +    if (
    
    757
    +      !Array.isArray(bridges.bridge_strings) ||
    
    758
    +      bridges.bridge_strings.some(str => typeof str !== "string")
    
    759
    +    ) {
    
    760
    +      throw new Error("bridge_strings should be an Array of strings");
    
    761
    +    }
    
    762
    +
    
    763
    +    if (
    
    764
    +      bridges.source !== TorBridgeSource.Invalid &&
    
    765
    +      !bridges.bridge_strings?.length
    
    766
    +    ) {
    
    767
    +      throw new Error(
    
    768
    +        `Missing bridge_strings for bridge source ${bridges.source}`
    
    769
    +      );
    
    770
    +    }
    
    771
    +
    
    772
    +    if (bridges.source !== TorBridgeSource.BuiltIn) {
    
    773
    +      bridges.builtin_type = "";
    
    774
    +    }
    
    775
    +    if (bridges.source !== TorBridgeSource.Lox) {
    
    776
    +      bridges.lox_id = "";
    
    777
    +    }
    
    778
    +
    
    779
    +    if (bridges.source === TorBridgeSource.Invalid) {
    
    780
    +      bridges.enabled = false;
    
    781
    +    }
    
    782
    +  }
    
    783
    +
    
    784
    +  /**
    
    785
    +   * Fixup the given proxy settings to fill in details, establish the correct
    
    786
    +   * types and clean up.
    
    787
    +   *
    
    788
    +   * May throw if there is an error in the given values.
    
    789
    +   *
    
    790
    +   * @param {Object} proxy - The proxy settings to fix up.
    
    791
    +   */
    
    792
    +  #fixupProxySettings(proxy) {
    
    793
    +    proxy.enabled = Boolean(proxy.enabled);
    
    794
    +    if (!proxy.enabled) {
    
    795
    +      proxy.type = TorProxyType.Invalid;
    
    796
    +      proxy.address = "";
    
    797
    +      proxy.port = 0;
    
    798
    +      proxy.username = "";
    
    799
    +      proxy.password = "";
    
    800
    +      return;
    
    801
    +    }
    
    802
    +
    
    803
    +    if (!Object.values(TorProxyType).includes(proxy.type)) {
    
    804
    +      throw new Error(`Invalid proxy type: ${proxy.type}`);
    
    805
    +    }
    
    806
    +    proxy.port = this.#parsePort(proxy.port, false);
    
    807
    +    proxy.address = String(proxy.address);
    
    808
    +    proxy.username = String(proxy.username);
    
    809
    +    proxy.password = String(proxy.password);
    
    810
    +  }
    
    811
    +
    
    812
    +  /**
    
    813
    +   * Fixup the given firewall settings to fill in details, establish the correct
    
    814
    +   * types and clean up.
    
    815
    +   *
    
    816
    +   * May throw if there is an error in the given values.
    
    817
    +   *
    
    818
    +   * @param {Object} firewall - The proxy settings to fix up.
    
    819
    +   */
    
    820
    +  #fixupFirewallSettings(firewall) {
    
    821
    +    firewall.enabled = Boolean(firewall.enabled);
    
    822
    +    if (!firewall.enabled) {
    
    823
    +      firewall.allowed_ports = [];
    
    824
    +      return;
    
    825
    +    }
    
    826
    +
    
    827
    +    let allowed_ports = firewall.allowed_ports;
    
    828
    +    if (!Array.isArray(allowed_ports)) {
    
    829
    +      allowed_ports = allowed_ports === "" ? [] : allowed_ports.split(",");
    
    830
    +    }
    
    831
    +    // parse and remove duplicates
    
    832
    +    const portSet = new Set();
    
    833
    +
    
    834
    +    for (const port of allowed_ports) {
    
    835
    +      try {
    
    836
    +        portSet.add(this.#parsePort(port, true));
    
    837
    +      } catch (e) {
    
    838
    +        // Do not throw for individual ports.
    
    839
    +        lazy.logger.error(`Failed to parse the port ${port}. Ignoring.`, e);
    
    840
    +      }
    
    841
    +    }
    
    842
    +    firewall.allowed_ports = [...portSet];
    
    843
    +  }
    
    844
    +
    
    985 845
       /**
    
    986 846
        * Change the Tor settings in use.
    
    987 847
        *
    
    ... ... @@ -994,101 +854,128 @@ class TorSettingsImpl {
    994 854
        * + proxy settings can be set as a group.
    
    995 855
        * + firewall settings can be set a group.
    
    996 856
        *
    
    997
    -   * @param {object} settings - The settings object to set.
    
    857
    +   * @param {object} newValues - The new setting values, a subset of the
    
    858
    +   *   complete settings that should be changed.
    
    998 859
        */
    
    999
    -  async changeSettings(settings) {
    
    1000
    -    lazy.logger.debug("changeSettings()", settings);
    
    860
    +  async changeSettings(newValues) {
    
    861
    +    lazy.logger.debug("changeSettings()", newValues);
    
    1001 862
         this.#checkIfInitialized();
    
    1002 863
     
    
    1003
    -    const backup = this.getSettings();
    
    1004
    -    const backupNotifications = [...this.#notificationQueue];
    
    1005
    -    // Start collecting errors.
    
    1006
    -    this.#settingErrors = [];
    
    1007
    -
    
    1008
    -    // Hold off on lots of notifications until all settings are changed.
    
    1009
    -    this.#freezeNotifications();
    
    1010
    -    try {
    
    1011
    -      if ("quickstart" in settings && "enabled" in settings.quickstart) {
    
    1012
    -        this.quickstart.enabled = !!settings.quickstart.enabled;
    
    864
    +    // Make a structured clone since we change the object and may adopt some of
    
    865
    +    // the Array values.
    
    866
    +    newValues = structuredClone(newValues);
    
    867
    +
    
    868
    +    const completeSettings = structuredClone(this.#settings);
    
    869
    +    const changes = [];
    
    870
    +
    
    871
    +    /**
    
    872
    +     * Change the given setting to a new value. Does nothing if the new value
    
    873
    +     * equals the old one, otherwise the change will be recorded in `changes`.
    
    874
    +     *
    
    875
    +     * @param {string} group - The group name for the property.
    
    876
    +     * @param {string} prop - The property name within the group.
    
    877
    +     * @param {any} value - The value to set.
    
    878
    +     * @param [Function?] equal - A method to test equality between the old and
    
    879
    +     *   new value. Otherwise uses `===` to check equality.
    
    880
    +     */
    
    881
    +    const changeSetting = (group, prop, value, equal = null) => {
    
    882
    +      const currentValue = this.#settings[group][prop];
    
    883
    +      if (equal ? equal(currentValue, value) : currentValue === value) {
    
    884
    +        return;
    
    1013 885
           }
    
    886
    +      completeSettings[group][prop] = value;
    
    887
    +      changes.push(`${group}.${prop}`);
    
    888
    +    };
    
    1014 889
     
    
    1015
    -      if ("bridges" in settings) {
    
    1016
    -        if ("enabled" in settings.bridges) {
    
    1017
    -          this.bridges.enabled = !!settings.bridges.enabled;
    
    1018
    -        }
    
    1019
    -        if ("source" in settings.bridges) {
    
    1020
    -          this.bridges.source = settings.bridges.source;
    
    1021
    -          switch (settings.bridges.source) {
    
    1022
    -            case TorBridgeSource.BridgeDB:
    
    1023
    -            case TorBridgeSource.UserProvided:
    
    1024
    -              this.bridges.bridge_strings = settings.bridges.bridge_strings;
    
    1025
    -              break;
    
    1026
    -            case TorBridgeSource.BuiltIn:
    
    1027
    -              this.bridges.builtin_type = settings.bridges.builtin_type;
    
    1028
    -              break;
    
    1029
    -            case TorBridgeSource.Lox:
    
    1030
    -              this.bridges.lox_id = settings.bridges.lox_id;
    
    1031
    -              break;
    
    1032
    -            case TorBridgeSource.Invalid:
    
    1033
    -              break;
    
    1034
    -            case undefined:
    
    1035
    -              break;
    
    1036
    -          }
    
    1037
    -        }
    
    1038
    -      }
    
    890
    +    if ("quickstart" in newValues && "enabled" in newValues.quickstart) {
    
    891
    +      changeSetting(
    
    892
    +        "quickstart",
    
    893
    +        "enabled",
    
    894
    +        Boolean(newValues.quickstart.enabled)
    
    895
    +      );
    
    896
    +    }
    
    1039 897
     
    
    1040
    -      if ("proxy" in settings) {
    
    1041
    -        // proxy settings have to be set as a group.
    
    1042
    -        this.proxy.enabled = !!settings.proxy.enabled;
    
    1043
    -        if (this.proxy.enabled) {
    
    1044
    -          this.proxy.type = settings.proxy.type;
    
    1045
    -          this.proxy.address = settings.proxy.address;
    
    1046
    -          this.proxy.port = settings.proxy.port;
    
    1047
    -          this.proxy.username = settings.proxy.username;
    
    1048
    -          this.proxy.password = settings.proxy.password;
    
    898
    +    if ("bridges" in newValues) {
    
    899
    +      if ("source" in newValues.bridges) {
    
    900
    +        this.#fixupBridgeSettings(newValues.bridges);
    
    901
    +        changeSetting("bridges", "source", newValues.bridges.source);
    
    902
    +        changeSetting(
    
    903
    +          "bridges",
    
    904
    +          "bridge_strings",
    
    905
    +          newValues.bridges.bridge_strings,
    
    906
    +          this.#arrayEqual
    
    907
    +        );
    
    908
    +        changeSetting("bridges", "lox_id", newValues.bridges.lox_id);
    
    909
    +        changeSetting(
    
    910
    +          "bridges",
    
    911
    +          "builtin_type",
    
    912
    +          newValues.bridges.builtin_type
    
    913
    +        );
    
    914
    +      } else if ("enabled" in newValues.bridges) {
    
    915
    +        // Don't need to fixup all the settings, just need to ensure that the
    
    916
    +        // enabled value is compatible with the current source.
    
    917
    +        newValues.bridges.enabled = Boolean(newValues.bridges.enabled);
    
    918
    +        if (
    
    919
    +          newValues.bridges.enabled &&
    
    920
    +          completeSettings.bridges.source === TorBridgeSource.Invalid
    
    921
    +        ) {
    
    922
    +          throw new Error("Cannot enable bridges without a bridge source.");
    
    1049 923
             }
    
    1050 924
           }
    
    1051
    -
    
    1052
    -      if ("firewall" in settings) {
    
    1053
    -        // firewall settings have to be set as a group.
    
    1054
    -        this.firewall.enabled = !!settings.firewall.enabled;
    
    1055
    -        if (this.firewall.enabled) {
    
    1056
    -          this.firewall.allowed_ports = settings.firewall.allowed_ports;
    
    1057
    -        }
    
    925
    +      if ("enabled" in newValues.bridges) {
    
    926
    +        changeSetting("bridges", "enabled", newValues.bridges.enabled);
    
    1058 927
           }
    
    928
    +    }
    
    1059 929
     
    
    1060
    -      this.#cleanupSettings();
    
    930
    +    if ("proxy" in newValues) {
    
    931
    +      // proxy settings have to be set as a group.
    
    932
    +      this.#fixupProxySettings(newValues.proxy);
    
    933
    +      changeSetting("proxy", "enabled", Boolean(newValues.proxy.enabled));
    
    934
    +      changeSetting("proxy", "type", newValues.proxy.type);
    
    935
    +      changeSetting("proxy", "address", newValues.proxy.address);
    
    936
    +      changeSetting("proxy", "port", newValues.proxy.port);
    
    937
    +      changeSetting("proxy", "username", newValues.proxy.username);
    
    938
    +      changeSetting("proxy", "password", newValues.proxy.password);
    
    939
    +    }
    
    1061 940
     
    
    1062
    -      if (this.#settingErrors.length) {
    
    1063
    -        throw Error(this.#settingErrors.join("; "));
    
    1064
    -      }
    
    1065
    -      this.#saveToPrefs();
    
    1066
    -    } catch (ex) {
    
    1067
    -      // Restore the old settings without any new notifications generated from
    
    1068
    -      // the above code.
    
    1069
    -      // NOTE: Since the code that changes #settings is not async, it should not
    
    1070
    -      // be possible for some other call to TorSettings to change anything
    
    1071
    -      // whilst we are in this context (other than lower down in this call
    
    1072
    -      // stack), so it is safe to discard all changes to settings and
    
    1073
    -      // notifications.
    
    1074
    -      this.#settings = backup;
    
    1075
    -      this.#notificationQueue.clear();
    
    1076
    -      for (const notification of backupNotifications) {
    
    1077
    -        this.#notificationQueue.add(notification);
    
    1078
    -      }
    
    941
    +    if ("firewall" in newValues) {
    
    942
    +      // firewall settings have to be set as a group.
    
    943
    +      this.#fixupFirewallSettings(newValues.firewall);
    
    944
    +      changeSetting("firewall", "enabled", Boolean(newValues.firewall.enabled));
    
    945
    +      changeSetting(
    
    946
    +        "firewall",
    
    947
    +        "allowed_ports",
    
    948
    +        newValues.firewall.allowed_ports,
    
    949
    +        this.#arrayEqual
    
    950
    +      );
    
    951
    +    }
    
    952
    +
    
    953
    +    // No errors so far, so save and commit.
    
    954
    +    this.#settings = completeSettings;
    
    955
    +    this.#saveToPrefs();
    
    1079 956
     
    
    1080
    -      throw ex;
    
    1081
    -    } finally {
    
    1082
    -      this.#thawNotifications();
    
    1083
    -      // Stop collecting errors.
    
    1084
    -      this.#settingErrors = null;
    
    957
    +    if (changes.length) {
    
    958
    +      Services.obs.notifyObservers(
    
    959
    +        { changes },
    
    960
    +        TorSettingsTopics.SettingsChanged
    
    961
    +      );
    
    1085 962
         }
    
    1086 963
     
    
    1087
    -    lazy.logger.debug("setSettings result", this.#settings);
    
    964
    +    lazy.logger.debug("setSettings result", this.#settings, changes);
    
    1088 965
     
    
    1089 966
         // After we have sent out the notifications for the changed settings and
    
    1090 967
         // saved the preferences we send the new settings to TorProvider.
    
    1091
    -    await this.#applySettings();
    
    968
    +    // Some properties are unread by TorProvider. So if only these values change
    
    969
    +    // there is no need to re-apply the settings.
    
    970
    +    const unreadProps = [
    
    971
    +      "quickstart.enabled",
    
    972
    +      "bridges.builtin_type",
    
    973
    +      "bridges.lox_id",
    
    974
    +    ];
    
    975
    +    const shouldApply = changes.some(prop => !unreadProps.includes(prop));
    
    976
    +    if (shouldApply) {
    
    977
    +      await this.#applySettings();
    
    978
    +    }
    
    1092 979
       }
    
    1093 980
     
    
    1094 981
       /**
    
    ... ... @@ -1147,29 +1034,11 @@ class TorSettingsImpl {
    1147 1034
         const bridgeSettings = {
    
    1148 1035
           enabled: true,
    
    1149 1036
           source: bridges.source,
    
    1037
    +      builtin_type: String(bridges.builtin_type),
    
    1038
    +      bridge_strings: structuredClone(bridges.bridge_strings),
    
    1150 1039
         };
    
    1151 1040
     
    
    1152
    -    if (bridges.source === TorBridgeSource.BuiltIn) {
    
    1153
    -      if (!bridges.builtin_type) {
    
    1154
    -        throw Error("Missing a built-in type");
    
    1155
    -      }
    
    1156
    -      bridgeSettings.builtin_type = String(bridges.builtin_type);
    
    1157
    -      const bridgeStrings = this.getBuiltinBridges(bridgeSettings.builtin_type);
    
    1158
    -      if (!bridgeStrings.length) {
    
    1159
    -        throw new Error(`No builtin bridges for type ${bridges.builtin_type}`);
    
    1160
    -      }
    
    1161
    -      bridgeSettings.bridge_strings = bridgeStrings;
    
    1162
    -    } else {
    
    1163
    -      // BridgeDB.
    
    1164
    -      if (!bridges.bridge_strings?.length) {
    
    1165
    -        throw new Error("Missing bridges strings");
    
    1166
    -      }
    
    1167
    -      // TODO: Can we safely verify the format of the bridge addresses sent from
    
    1168
    -      // Moat?
    
    1169
    -      bridgeSettings.bridge_strings = Array.from(bridges.bridge_strings, item =>
    
    1170
    -        String(item)
    
    1171
    -      );
    
    1172
    -    }
    
    1041
    +    this.#fixupBridgeSettings(bridgeSettings);
    
    1173 1042
     
    
    1174 1043
         // After checks are complete, we commit them.
    
    1175 1044
         this.#temporaryBridgeSettings = bridgeSettings;