| ... | 
... | 
@@ -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;
  |