tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_libPrefs.js (30778B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // It is necessary to manually disable `xpc::IsInAutomation` since
      6 // `resetPrefs` will flip the preference to re-enable `once`-synced
      7 // preference change assertions, and also change the value of those
      8 // preferences.
      9 Services.prefs.setBoolPref(
     10  "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer",
     11  false
     12 );
     13 
     14 const PREF_INVALID = 0;
     15 const PREF_BOOL = 128;
     16 const PREF_INT = 64;
     17 const PREF_STRING = 32;
     18 
     19 const MAX_PREF_LENGTH = 1 * 1024 * 1024;
     20 
     21 function makeList(a) {
     22  var o = {};
     23  for (var i = 0; i < a.length; i++) {
     24    o[a[i]] = "";
     25  }
     26  return o;
     27 }
     28 
     29 add_task(async function run_test() {
     30  const ps = Services.prefs;
     31 
     32  //* *************************************************************************//
     33  // Nullsafety
     34 
     35  do_check_throws(function () {
     36    ps.getPrefType(null);
     37  }, Cr.NS_ERROR_INVALID_ARG);
     38  do_check_throws(function () {
     39    ps.getBoolPref(null);
     40  }, Cr.NS_ERROR_INVALID_ARG);
     41  do_check_throws(function () {
     42    ps.setBoolPref(null, false);
     43  }, Cr.NS_ERROR_INVALID_ARG);
     44  do_check_throws(function () {
     45    ps.getIntPref(null);
     46  }, Cr.NS_ERROR_INVALID_ARG);
     47  do_check_throws(function () {
     48    ps.setIntPref(null, 0);
     49  }, Cr.NS_ERROR_INVALID_ARG);
     50  do_check_throws(function () {
     51    ps.getCharPref(null);
     52  }, Cr.NS_ERROR_INVALID_ARG);
     53  do_check_throws(function () {
     54    ps.setCharPref(null, null);
     55  }, Cr.NS_ERROR_INVALID_ARG);
     56  do_check_throws(function () {
     57    ps.getStringPref(null);
     58  }, Cr.NS_ERROR_INVALID_ARG);
     59  do_check_throws(function () {
     60    ps.setStringPref(null, null);
     61  }, Cr.NS_ERROR_INVALID_ARG);
     62  do_check_throws(function () {
     63    ps.clearUserPref(null);
     64  }, Cr.NS_ERROR_INVALID_ARG);
     65  do_check_throws(function () {
     66    ps.prefHasUserValue(null);
     67  }, Cr.NS_ERROR_INVALID_ARG);
     68  do_check_throws(function () {
     69    ps.lockPref(null);
     70  }, Cr.NS_ERROR_INVALID_ARG);
     71  do_check_throws(function () {
     72    ps.prefIsLocked(null);
     73  }, Cr.NS_ERROR_INVALID_ARG);
     74  do_check_throws(function () {
     75    ps.unlockPref(null);
     76  }, Cr.NS_ERROR_INVALID_ARG);
     77  do_check_throws(function () {
     78    ps.deleteBranch(null);
     79  }, Cr.NS_ERROR_INVALID_ARG);
     80  do_check_throws(function () {
     81    ps.getChildList(null);
     82  }, Cr.NS_ERROR_INVALID_ARG);
     83 
     84  //* *************************************************************************//
     85  // Nonexisting user preferences
     86 
     87  Assert.equal(ps.prefHasUserValue("UserPref.nonexistent.hasUserValue"), false);
     88  ps.clearUserPref("UserPref.nonexistent.clearUserPref"); // shouldn't throw
     89  Assert.equal(
     90    ps.getPrefType("UserPref.nonexistent.getPrefType"),
     91    PREF_INVALID
     92  );
     93  Assert.equal(ps.root, "");
     94 
     95  // bool...
     96  do_check_throws(function () {
     97    ps.getBoolPref("UserPref.nonexistent.getBoolPref");
     98  }, Cr.NS_ERROR_UNEXPECTED);
     99  ps.setBoolPref("UserPref.nonexistent.setBoolPref", false);
    100  Assert.equal(ps.getBoolPref("UserPref.nonexistent.setBoolPref"), false);
    101 
    102  // int...
    103  do_check_throws(function () {
    104    ps.getIntPref("UserPref.nonexistent.getIntPref");
    105  }, Cr.NS_ERROR_UNEXPECTED);
    106  ps.setIntPref("UserPref.nonexistent.setIntPref", 5);
    107  Assert.equal(ps.getIntPref("UserPref.nonexistent.setIntPref"), 5);
    108 
    109  // char
    110  do_check_throws(function () {
    111    ps.getCharPref("UserPref.nonexistent.getCharPref");
    112  }, Cr.NS_ERROR_UNEXPECTED);
    113  ps.setCharPref("UserPref.nonexistent.setCharPref", "_test");
    114  Assert.equal(ps.getCharPref("UserPref.nonexistent.setCharPref"), "_test");
    115 
    116  //* *************************************************************************//
    117  // Existing user Prefs and data integrity test (round-trip match)
    118 
    119  ps.setBoolPref("UserPref.existing.bool", true);
    120  ps.setIntPref("UserPref.existing.int", 23);
    121  ps.setCharPref("UserPref.existing.char", "hey");
    122 
    123  // getPref should return the pref value
    124  Assert.equal(ps.getBoolPref("UserPref.existing.bool"), true);
    125  Assert.equal(ps.getIntPref("UserPref.existing.int"), 23);
    126  Assert.equal(ps.getCharPref("UserPref.existing.char"), "hey");
    127 
    128  // setPref should not complain and should change the value of the pref
    129  ps.setBoolPref("UserPref.existing.bool", false);
    130  Assert.equal(ps.getBoolPref("UserPref.existing.bool"), false);
    131  ps.setIntPref("UserPref.existing.int", 24);
    132  Assert.equal(ps.getIntPref("UserPref.existing.int"), 24);
    133  ps.setCharPref("UserPref.existing.char", "hej då!");
    134  Assert.equal(ps.getCharPref("UserPref.existing.char"), "hej då!");
    135 
    136  // prefHasUserValue should return true now
    137  Assert.ok(ps.prefHasUserValue("UserPref.existing.bool"));
    138  Assert.ok(ps.prefHasUserValue("UserPref.existing.int"));
    139  Assert.ok(ps.prefHasUserValue("UserPref.existing.char"));
    140 
    141  // clearUserPref should remove the pref
    142  ps.clearUserPref("UserPref.existing.bool");
    143  Assert.ok(!ps.prefHasUserValue("UserPref.existing.bool"));
    144  ps.clearUserPref("UserPref.existing.int");
    145  Assert.ok(!ps.prefHasUserValue("UserPref.existing.int"));
    146  ps.clearUserPref("UserPref.existing.char");
    147  Assert.ok(!ps.prefHasUserValue("UserPref.existing.char"));
    148 
    149  //* *************************************************************************//
    150  // Large value test
    151 
    152  let largeStr = new Array(MAX_PREF_LENGTH + 1).join("x");
    153  ps.setCharPref("UserPref.large.char", largeStr);
    154  largeStr += "x";
    155  do_check_throws(function () {
    156    ps.setCharPref("UserPref.large.char", largeStr);
    157  }, Cr.NS_ERROR_ILLEGAL_VALUE);
    158 
    159  //* *************************************************************************//
    160  // getPrefType test
    161 
    162  // bool...
    163  ps.setBoolPref("UserPref.getPrefType.bool", true);
    164  Assert.equal(ps.getPrefType("UserPref.getPrefType.bool"), PREF_BOOL);
    165 
    166  // int...
    167  ps.setIntPref("UserPref.getPrefType.int", -234);
    168  Assert.equal(ps.getPrefType("UserPref.getPrefType.int"), PREF_INT);
    169 
    170  // char...
    171  ps.setCharPref("UserPref.getPrefType.char", "testing1..2");
    172  Assert.equal(ps.getPrefType("UserPref.getPrefType.char"), PREF_STRING);
    173 
    174  //* *************************************************************************//
    175  // getBranch tests
    176 
    177  Assert.equal(ps.root, "");
    178 
    179  // bool ...
    180  ps.setBoolPref("UserPref.root.boolPref", true);
    181  let pb_1 = ps.getBranch("UserPref.root.");
    182  Assert.equal(pb_1.getBoolPref("boolPref"), true);
    183  let pb_2 = ps.getBranch("UserPref.root.boolPref");
    184  Assert.equal(pb_2.getBoolPref(""), true);
    185  pb_2.setBoolPref(".anotherPref", false);
    186  let pb_3 = ps.getBranch("UserPref.root.boolPre");
    187  Assert.equal(pb_3.getBoolPref("f.anotherPref"), false);
    188 
    189  // int ...
    190  ps.setIntPref("UserPref.root.intPref", 23);
    191  pb_1 = ps.getBranch("UserPref.root.");
    192  Assert.equal(pb_1.getIntPref("intPref"), 23);
    193  pb_2 = ps.getBranch("UserPref.root.intPref");
    194  Assert.equal(pb_2.getIntPref(""), 23);
    195  pb_2.setIntPref(".anotherPref", 69);
    196  pb_3 = ps.getBranch("UserPref.root.intPre");
    197  Assert.equal(pb_3.getIntPref("f.anotherPref"), 69);
    198 
    199  // char...
    200  ps.setCharPref("UserPref.root.charPref", "_char");
    201  pb_1 = ps.getBranch("UserPref.root.");
    202  Assert.equal(pb_1.getCharPref("charPref"), "_char");
    203  pb_2 = ps.getBranch("UserPref.root.charPref");
    204  Assert.equal(pb_2.getCharPref(""), "_char");
    205  pb_2.setCharPref(".anotherPref", "_another");
    206  pb_3 = ps.getBranch("UserPref.root.charPre");
    207  Assert.equal(pb_3.getCharPref("f.anotherPref"), "_another");
    208 
    209  //* *************************************************************************//
    210  // getChildlist tests
    211 
    212  // get an already set prefBranch
    213  let pb1 = ps.getBranch("UserPref.root.");
    214  let prefList = pb1.getChildList("");
    215  Assert.equal(prefList.length, 6);
    216 
    217  // check for specific prefs in the array : the order is not important
    218  Assert.ok("boolPref" in makeList(prefList));
    219  Assert.ok("intPref" in makeList(prefList));
    220  Assert.ok("charPref" in makeList(prefList));
    221  Assert.ok("boolPref.anotherPref" in makeList(prefList));
    222  Assert.ok("intPref.anotherPref" in makeList(prefList));
    223  Assert.ok("charPref.anotherPref" in makeList(prefList));
    224 
    225  //* *************************************************************************//
    226  // Default branch tests
    227 
    228  // bool...
    229  pb1 = ps.getDefaultBranch("");
    230  pb1.setBoolPref("DefaultPref.bool", true);
    231  Assert.equal(pb1.getBoolPref("DefaultPref.bool"), true);
    232  Assert.ok(!pb1.prefHasUserValue("DefaultPref.bool"));
    233  ps.setBoolPref("DefaultPref.bool", false);
    234  Assert.ok(pb1.prefHasUserValue("DefaultPref.bool"));
    235  Assert.equal(ps.getBoolPref("DefaultPref.bool"), false);
    236 
    237  // int...
    238  pb1 = ps.getDefaultBranch("");
    239  pb1.setIntPref("DefaultPref.int", 100);
    240  Assert.equal(pb1.getIntPref("DefaultPref.int"), 100);
    241  Assert.ok(!pb1.prefHasUserValue("DefaultPref.int"));
    242  ps.setIntPref("DefaultPref.int", 50);
    243  Assert.ok(pb1.prefHasUserValue("DefaultPref.int"));
    244  Assert.equal(ps.getIntPref("DefaultPref.int"), 50);
    245 
    246  // char...
    247  pb1 = ps.getDefaultBranch("");
    248  pb1.setCharPref("DefaultPref.char", "_default");
    249  Assert.equal(pb1.getCharPref("DefaultPref.char"), "_default");
    250  Assert.ok(!pb1.prefHasUserValue("DefaultPref.char"));
    251  ps.setCharPref("DefaultPref.char", "_user");
    252  Assert.ok(pb1.prefHasUserValue("DefaultPref.char"));
    253  Assert.equal(ps.getCharPref("DefaultPref.char"), "_user");
    254 
    255  //* *************************************************************************//
    256  // pref Locking/Unlocking tests
    257 
    258  // locking and unlocking a nonexistent pref should throw
    259  do_check_throws(function () {
    260    ps.lockPref("DefaultPref.nonexistent");
    261  }, Cr.NS_ERROR_ILLEGAL_VALUE);
    262  do_check_throws(function () {
    263    ps.unlockPref("DefaultPref.nonexistent");
    264  }, Cr.NS_ERROR_ILLEGAL_VALUE);
    265 
    266  // getting a locked pref branch should return the "default" value
    267  Assert.ok(!ps.prefIsLocked("DefaultPref.char"));
    268  ps.lockPref("DefaultPref.char");
    269  Assert.equal(ps.getCharPref("DefaultPref.char"), "_default");
    270  Assert.ok(ps.prefIsLocked("DefaultPref.char"));
    271 
    272  // getting an unlocked pref branch should return the "user" value
    273  ps.unlockPref("DefaultPref.char");
    274  Assert.equal(ps.getCharPref("DefaultPref.char"), "_user");
    275  Assert.ok(!ps.prefIsLocked("DefaultPref.char"));
    276 
    277  // setting the "default" value to a user pref branch should
    278  // make prefHasUserValue return false (documented behavior)
    279  ps.setCharPref("DefaultPref.char", "_default");
    280  Assert.ok(!pb1.prefHasUserValue("DefaultPref.char"));
    281 
    282  // unlocking and locking multiple times shouldn't throw
    283  ps.unlockPref("DefaultPref.char");
    284  ps.lockPref("DefaultPref.char");
    285  ps.lockPref("DefaultPref.char");
    286 
    287  //* *************************************************************************//
    288  // deleteBranch tests
    289 
    290  // TODO : Really, this should throw!, by documentation.
    291  // do_check_throws(function() {
    292  // ps.deleteBranch("UserPref.nonexistent.deleteBranch");}, Cr.NS_ERROR_UNEXPECTED);
    293 
    294  ps.deleteBranch("DefaultPref");
    295  let pb = ps.getBranch("DefaultPref");
    296  pb1 = ps.getDefaultBranch("DefaultPref");
    297 
    298  // getting prefs on deleted user branches should throw
    299  do_check_throws(function () {
    300    pb.getBoolPref("DefaultPref.bool");
    301  }, Cr.NS_ERROR_UNEXPECTED);
    302  do_check_throws(function () {
    303    pb.getIntPref("DefaultPref.int");
    304  }, Cr.NS_ERROR_UNEXPECTED);
    305  do_check_throws(function () {
    306    pb.getCharPref("DefaultPref.char");
    307  }, Cr.NS_ERROR_UNEXPECTED);
    308 
    309  // getting prefs on deleted default branches should throw
    310  do_check_throws(function () {
    311    pb1.getBoolPref("DefaultPref.bool");
    312  }, Cr.NS_ERROR_UNEXPECTED);
    313  do_check_throws(function () {
    314    pb1.getIntPref("DefaultPref.int");
    315  }, Cr.NS_ERROR_UNEXPECTED);
    316  do_check_throws(function () {
    317    pb1.getCharPref("DefaultPref.char");
    318  }, Cr.NS_ERROR_UNEXPECTED);
    319 
    320  //* *************************************************************************//
    321  // savePrefFile & readPrefFile tests
    322 
    323  // set some prefs
    324  ps.setBoolPref("ReadPref.bool", true);
    325  ps.setIntPref("ReadPref.int", 230);
    326  ps.setCharPref("ReadPref.char", "hello");
    327 
    328  // save those prefs in a file
    329  let savePrefFile = do_get_cwd();
    330  savePrefFile.append("data");
    331  savePrefFile.append("savePref.js");
    332 
    333  if (savePrefFile.exists()) {
    334    savePrefFile.remove(false);
    335  }
    336  savePrefFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
    337  ps.savePrefFile(savePrefFile);
    338  ps.resetPrefs();
    339 
    340  // load a preexisting pref file
    341  let prefFile = do_get_file("data/testPref.js");
    342  ps.readUserPrefsFromFile(prefFile);
    343 
    344  // former prefs should have been replaced/lost
    345  do_check_throws(function () {
    346    pb.getBoolPref("ReadPref.bool");
    347  }, Cr.NS_ERROR_UNEXPECTED);
    348  do_check_throws(function () {
    349    pb.getIntPref("ReadPref.int");
    350  }, Cr.NS_ERROR_UNEXPECTED);
    351  do_check_throws(function () {
    352    pb.getCharPref("ReadPref.char");
    353  }, Cr.NS_ERROR_UNEXPECTED);
    354 
    355  // loaded prefs should read ok.
    356  pb = ps.getBranch("testPref.");
    357  Assert.equal(pb.getBoolPref("bool1"), true);
    358  Assert.equal(pb.getBoolPref("bool2"), false);
    359  Assert.equal(pb.getIntPref("int1"), 23);
    360  Assert.equal(pb.getIntPref("int2"), -1236);
    361  Assert.equal(pb.getCharPref("char1"), "_testPref");
    362  Assert.equal(pb.getCharPref("char2"), "älskar");
    363 
    364  // loading our former savePrefFile should allow us to read former prefs
    365 
    366  // Hack alert: on Windows nsLocalFile caches the size of savePrefFile from
    367  // the .create() call above as 0. We call .exists() to reset the cache.
    368  savePrefFile.exists();
    369 
    370  ps.readUserPrefsFromFile(savePrefFile);
    371  // cleanup the file now we don't need it
    372  savePrefFile.remove(false);
    373  Assert.equal(ps.getBoolPref("ReadPref.bool"), true);
    374  Assert.equal(ps.getIntPref("ReadPref.int"), 230);
    375  Assert.equal(ps.getCharPref("ReadPref.char"), "hello");
    376 
    377  // ... and still be able to access "prior-to-readUserPrefs" preferences
    378  Assert.equal(pb.getBoolPref("bool1"), true);
    379  Assert.equal(pb.getBoolPref("bool2"), false);
    380  Assert.equal(pb.getIntPref("int1"), 23);
    381 
    382  //* *************************************************************************//
    383  // preference Observers
    384 
    385  class PrefObserver {
    386    /**
    387     * Creates and registers a pref observer.
    388     *
    389     * @param prefBranch The preference branch instance to observe.
    390     * @param expectedName The pref name we expect to receive.
    391     * @param expectedValue The int pref value we expect to receive.
    392     * @param finishedResolve A function that is called when the test is finished.
    393     */
    394    constructor(prefBranch, expectedName, expectedValue, finishedResolve) {
    395      this.pb = prefBranch;
    396      this.name = expectedName;
    397      this.value = expectedValue;
    398      this.finishedResolve = finishedResolve;
    399      this.resolveCalls = 0;
    400 
    401      prefBranch.addObserver(expectedName, this);
    402    }
    403 
    404    observe(aSubject, aTopic, aState) {
    405      Assert.equal(aTopic, "nsPref:changed");
    406      Assert.equal(aState, this.name);
    407      Assert.equal(this.pb.getIntPref(aState), this.value);
    408      pb.removeObserver(aState, this);
    409 
    410      // notification received, we may go on...
    411      this.resolveCalls++;
    412      this.finishedResolve();
    413    }
    414  }
    415  PrefObserver.QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
    416 
    417  let promiseResolvers = Promise.withResolvers();
    418  let observer = new PrefObserver(
    419    ps,
    420    "ReadPref.int",
    421    76,
    422    promiseResolvers.resolve
    423  );
    424  ps.setIntPref("ReadPref.int", 76);
    425  await promiseResolvers.promise;
    426 
    427  // removed observer should not fire
    428  ps.removeObserver("ReadPref.int", observer);
    429  ps.setIntPref("ReadPref.int", 32);
    430 
    431  // let's test observers once more with a non-root prefbranch
    432  pb = ps.getBranch("ReadPref.");
    433  promiseResolvers = Promise.withResolvers();
    434  let newObserver = new PrefObserver(pb, "int", 76, promiseResolvers.resolve);
    435  ps.setIntPref("ReadPref.int", 76);
    436  await promiseResolvers.promise;
    437 
    438  // Let's try that again with different pref.
    439  promiseResolvers = Promise.withResolvers();
    440  // Disabling no-unused-vars because newObserver is implicitly used
    441  // via promiseResolvers
    442  // eslint-disable-next-line no-unused-vars
    443  newObserver = new PrefObserver(
    444    pb,
    445    "another_int",
    446    76,
    447    promiseResolvers.resolve
    448  );
    449  ps.setIntPref("ReadPref.another_int", 76);
    450  await promiseResolvers.promise;
    451 
    452  // make sure the removed observer hasn't fired again
    453  Assert.equal(
    454    observer.resolveCalls,
    455    1,
    456    "Observer should not be called after removal"
    457  );
    458 });
    459 
    460 //* *************************************************************************//
    461 // deleteBranch observer notification tests
    462 
    463 /**
    464 * Tests that observers are notified when preferences are deleted via deleteBranch().
    465 */
    466 add_task(function test_deleteBranch_observers() {
    467  const ps = Services.prefs;
    468 
    469  // Set up test preferences
    470  ps.setBoolPref("DeleteTest.branch1.bool", true);
    471  ps.setIntPref("DeleteTest.branch1.int", 42);
    472  ps.setCharPref("DeleteTest.branch1.char", "test");
    473  ps.setBoolPref("DeleteTest.branch2.bool", false);
    474  ps.setCharPref("DeleteTest.other", "other");
    475 
    476  class DeleteObserver {
    477    constructor() {
    478      this.notifications = [];
    479    }
    480 
    481    observe(aSubject, aTopic, aData) {
    482      Assert.equal(aTopic, "nsPref:changed");
    483      this.notifications.push({
    484        subject: aSubject,
    485        topic: aTopic,
    486        data: aData,
    487      });
    488    }
    489  }
    490  DeleteObserver.QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
    491 
    492  // Test 1: Observer on root branch should see all deletions
    493  let rootObserver = new DeleteObserver();
    494  ps.addObserver("DeleteTest.", rootObserver);
    495 
    496  // Test 2: Observer on specific branch should only see that branch's deletions
    497  let branchObserver = new DeleteObserver();
    498  let branch1 = ps.getBranch("DeleteTest.branch1.");
    499  branch1.addObserver("", branchObserver);
    500 
    501  // Test 3: Observer on specific preference should only see that preference's deletion
    502  let prefObserver = new DeleteObserver();
    503  ps.addObserver("DeleteTest.branch1.bool", prefObserver);
    504 
    505  // Delete the branch1 subtree
    506  ps.deleteBranch("DeleteTest.branch1");
    507 
    508  // Verify root observer received notifications for all deleted prefs in branch1
    509  Assert.equal(
    510    rootObserver.notifications.length,
    511    3,
    512    "Root observer should receive 3 notifications"
    513  );
    514 
    515  let expectedPrefs = [
    516    "DeleteTest.branch1.bool",
    517    "DeleteTest.branch1.char",
    518    "DeleteTest.branch1.int",
    519  ];
    520  let receivedPrefs = rootObserver.notifications.map(n => n.data).sort();
    521  Assert.deepEqual(
    522    receivedPrefs,
    523    expectedPrefs,
    524    "Root observer should receive correct pref names"
    525  );
    526 
    527  // Verify all notifications have correct topic and subject
    528  for (let notification of rootObserver.notifications) {
    529    Assert.equal(
    530      notification.topic,
    531      "nsPref:changed",
    532      "Topic should be nsPref:changed"
    533    );
    534    Assert.ok(
    535      notification.subject instanceof Ci.nsIPrefBranch,
    536      "Subject should be nsIPrefBranch"
    537    );
    538    Assert.ok(
    539      !notification.subject.root,
    540      "Subject root should be falsy for root observer"
    541    );
    542  }
    543 
    544  // Verify branch observer received notifications for branch1 prefs (without prefix)
    545  Assert.equal(
    546    branchObserver.notifications.length,
    547    3,
    548    "Branch observer should receive 3 notifications"
    549  );
    550  let expectedBranchNames = ["bool", "char", "int"];
    551  let receivedBranchNames = branchObserver.notifications
    552    .map(n => n.data)
    553    .sort();
    554  Assert.deepEqual(
    555    receivedBranchNames,
    556    expectedBranchNames,
    557    "Branch observer should receive pref names relative to branch root"
    558  );
    559 
    560  // Verify specific pref observer received exactly one notification
    561  Assert.equal(
    562    prefObserver.notifications.length,
    563    1,
    564    "Specific pref observer should receive 1 notification"
    565  );
    566  Assert.equal(
    567    prefObserver.notifications[0].data,
    568    "DeleteTest.branch1.bool",
    569    "Specific pref observer should receive correct pref name"
    570  );
    571 
    572  // Verify the preferences were actually deleted
    573  assertPrefNotExists(
    574    "DeleteTest.branch1.bool",
    575    "Deleted boolean pref should throw when accessed",
    576    "getBoolPref"
    577  );
    578  assertPrefNotExists(
    579    "DeleteTest.branch1.int",
    580    "Deleted integer pref should throw when accessed"
    581  );
    582  assertPrefNotExists(
    583    "DeleteTest.branch1.char",
    584    "Deleted char pref should throw when accessed",
    585    "getCharPref"
    586  );
    587 
    588  // Verify other preferences were not affected
    589  assertPrefExists(
    590    "DeleteTest.branch2.bool",
    591    false,
    592    "Unrelated preferences should not be affected",
    593    "getBoolPref"
    594  );
    595  assertPrefExists(
    596    "DeleteTest.other",
    597    "other",
    598    "Unrelated preferences should not be affected",
    599    "getCharPref"
    600  );
    601 
    602  // Clean up observers
    603  ps.removeObserver("DeleteTest.", rootObserver);
    604  branch1.removeObserver("", branchObserver);
    605  ps.removeObserver("DeleteTest.branch1.bool", prefObserver);
    606 
    607  // Clean up remaining test preferences
    608  ps.deleteBranch("DeleteTest");
    609 });
    610 
    611 /**
    612 * Tests observer notifications when deleting an empty branch.
    613 * This edge case ensures that no spurious notifications are sent.
    614 */
    615 add_task(function test_deleteBranch_empty_branch() {
    616  const ps = Services.prefs;
    617 
    618  let observer = {
    619    notifications: [],
    620    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    621    observe(_aSubject, aTopic, aData) {
    622      this.notifications.push({ topic: aTopic, data: aData });
    623    },
    624  };
    625 
    626  ps.addObserver("EmptyBranch.", observer);
    627 
    628  // Delete a non-existent branch - should not generate notifications
    629  ps.deleteBranch("EmptyBranch");
    630 
    631  Assert.equal(
    632    observer.notifications.length,
    633    0,
    634    "Deleting empty/non-existent branch should not trigger observer notifications"
    635  );
    636 
    637  ps.removeObserver("EmptyBranch.", observer);
    638 });
    639 
    640 /**
    641 * Tests observer notifications when deleting a branch with both user and default values.
    642 * This ensures that both user and default preference deletions trigger notifications.
    643 */
    644 add_task(function test_deleteBranch_user_and_default_values() {
    645  const ps = Services.prefs;
    646 
    647  // Set up preferences with both default and user values
    648  let defaultBranch = ps.getDefaultBranch("");
    649  defaultBranch.setBoolPref("MixedTest.pref1", false);
    650  defaultBranch.setIntPref("MixedTest.pref2", 10);
    651 
    652  // Override with user values
    653  ps.setBoolPref("MixedTest.pref1", true);
    654  ps.setIntPref("MixedTest.pref2", 20);
    655 
    656  // Add user-only pref
    657  ps.setCharPref("MixedTest.pref3", "user-only");
    658 
    659  let observer = {
    660    notifications: [],
    661    QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    662    observe(aSubject, aTopic, aData) {
    663      this.notifications.push({ topic: aTopic, data: aData });
    664    },
    665  };
    666 
    667  ps.addObserver("MixedTest.", observer);
    668 
    669  // Delete the entire branch
    670  ps.deleteBranch("MixedTest");
    671 
    672  // Should receive notifications for all preferences (both user and default values get cleared)
    673  Assert.equal(
    674    observer.notifications.length,
    675    3,
    676    "Should receive notifications for all preferences with any values"
    677  );
    678 
    679  let receivedPrefs = observer.notifications.map(n => n.data).sort();
    680  let expectedPrefs = ["MixedTest.pref1", "MixedTest.pref2", "MixedTest.pref3"];
    681  Assert.deepEqual(
    682    receivedPrefs,
    683    expectedPrefs,
    684    "Should receive notifications for all deleted preferences"
    685  );
    686 
    687  // Verify all preferences are actually deleted
    688  assertPrefNotExists(
    689    "MixedTest.pref1",
    690    "Pref with default value should be completely deleted",
    691    "getBoolPref"
    692  );
    693  assertPrefNotExists(
    694    "MixedTest.pref2",
    695    "Pref with default value should be completely deleted"
    696  );
    697  assertPrefNotExists(
    698    "MixedTest.pref3",
    699    "User-only pref should be deleted",
    700    "getCharPref"
    701  );
    702 
    703  ps.removeObserver("MixedTest.", observer);
    704 });
    705 
    706 /**
    707 * Tests that weak observers are properly notified during branch deletion.
    708 */
    709 add_task(function test_deleteBranch_weak_observers() {
    710  const ps = Services.prefs;
    711 
    712  // Set up test preference
    713  ps.setBoolPref("WeakTest.pref", true);
    714 
    715  let observer = {
    716    notifications: [],
    717    QueryInterface: ChromeUtils.generateQI([
    718      "nsIObserver",
    719      "nsISupportsWeakReference",
    720    ]),
    721    observe(aSubject, aTopic, aData) {
    722      this.notifications.push({ topic: aTopic, data: aData });
    723    },
    724  };
    725 
    726  // Add as weak observer
    727  ps.addObserver("WeakTest.", observer, true);
    728 
    729  // Delete the branch
    730  ps.deleteBranch("WeakTest");
    731 
    732  // Weak observer should still receive notifications
    733  Assert.equal(
    734    observer.notifications.length,
    735    1,
    736    "Weak observer should receive deletion notification"
    737  );
    738  Assert.equal(
    739    observer.notifications[0].data,
    740    "WeakTest.pref",
    741    "Weak observer should receive correct pref name"
    742  );
    743 
    744  ps.removeObserver("WeakTest.", observer);
    745 });
    746 
    747 /**
    748 * Helper function to assert that a preference exists with the expected value.
    749 *
    750 * @param {string} prefName - The preference name
    751 * @param {*} expectedValue - The expected value
    752 * @param {string} message - The assertion message
    753 * @param {Function} [getter=getIntPref] - The preference getter function (e.g., getIntPref, getBoolPref, getCharPref)
    754 */
    755 function assertPrefExists(
    756  prefName,
    757  expectedValue,
    758  message,
    759  getter = "getIntPref"
    760 ) {
    761  Assert.equal(Services.prefs[getter](prefName), expectedValue, message);
    762 }
    763 
    764 /**
    765 * Helper function to assert that a preference does not exist.
    766 *
    767 * @param {string} prefName - The preference name
    768 * @param {string} message - The assertion message
    769 * @param {Function} [getter=getIntPref] - The preference getter function (e.g., getIntPref, getBoolPref, getCharPref)
    770 */
    771 function assertPrefNotExists(prefName, message, getter = "getIntPref") {
    772  Assert.throws(
    773    () => Services.prefs[getter](prefName),
    774    /NS_ERROR_UNEXPECTED/,
    775    message
    776  );
    777 }
    778 
    779 /**
    780 * Tests specific edge cases for deleteBranch behavior with consecutive dots
    781 */
    782 add_task(function test_deleteBranch_edge_cases() {
    783  const ps = Services.prefs;
    784 
    785  // Test case 1: deleteBranch("foo") deletes all preferences
    786  ps.setIntPref("EdgeTest.foo", 1);
    787  ps.setIntPref("EdgeTest.foo.", 2);
    788  ps.setIntPref("EdgeTest.foo.bar", 3);
    789  ps.setIntPref("EdgeTest.foo..", 4);
    790  ps.setIntPref("EdgeTest.foo..baz", 5);
    791 
    792  ps.deleteBranch("EdgeTest.foo");
    793 
    794  assertPrefNotExists(
    795    "EdgeTest.foo",
    796    "EdgeTest.foo should be deleted by deleteBranch('EdgeTest.foo')"
    797  );
    798  assertPrefNotExists(
    799    "EdgeTest.foo.",
    800    "EdgeTest.foo. should be deleted by deleteBranch('EdgeTest.foo')"
    801  );
    802  assertPrefNotExists(
    803    "EdgeTest.foo.bar",
    804    "EdgeTest.foo.bar should be deleted by deleteBranch('EdgeTest.foo')"
    805  );
    806  assertPrefNotExists(
    807    "EdgeTest.foo..",
    808    "EdgeTest.foo.. should be deleted by deleteBranch('EdgeTest.foo')"
    809  );
    810  assertPrefNotExists(
    811    "EdgeTest.foo..baz",
    812    "EdgeTest.foo..baz should be deleted by deleteBranch('EdgeTest.foo')"
    813  );
    814 
    815  // Test case 2: deleteBranch("foo.") also deletes all preferences
    816  ps.setIntPref("EdgeTest.foo", 1);
    817  ps.setIntPref("EdgeTest.foo.", 2);
    818  ps.setIntPref("EdgeTest.foo.bar", 3);
    819  ps.setIntPref("EdgeTest.foo..", 4);
    820  ps.setIntPref("EdgeTest.foo..baz", 5);
    821 
    822  ps.deleteBranch("EdgeTest.foo.");
    823 
    824  assertPrefNotExists(
    825    "EdgeTest.foo",
    826    "EdgeTest.foo should be deleted by deleteBranch('EdgeTest.foo.')"
    827  );
    828  assertPrefNotExists(
    829    "EdgeTest.foo.",
    830    "EdgeTest.foo. should be deleted by deleteBranch('EdgeTest.foo.')"
    831  );
    832  assertPrefNotExists(
    833    "EdgeTest.foo.bar",
    834    "EdgeTest.foo.bar should be deleted by deleteBranch('EdgeTest.foo.')"
    835  );
    836  assertPrefNotExists(
    837    "EdgeTest.foo..",
    838    "EdgeTest.foo.. should be deleted by deleteBranch('EdgeTest.foo.')"
    839  );
    840  assertPrefNotExists(
    841    "EdgeTest.foo..baz",
    842    "EdgeTest.foo..baz should be deleted by deleteBranch('EdgeTest.foo.')"
    843  );
    844 
    845  // Test case 3: deleteBranch("foo..") deletes only "foo.", "foo..", and "foo..baz"
    846  ps.setIntPref("EdgeTest.foo", 1);
    847  ps.setIntPref("EdgeTest.foo.", 2);
    848  ps.setIntPref("EdgeTest.foo.bar", 3);
    849  ps.setIntPref("EdgeTest.foo..", 4);
    850  ps.setIntPref("EdgeTest.foo..baz", 5);
    851 
    852  ps.deleteBranch("EdgeTest.foo..");
    853 
    854  assertPrefExists(
    855    "EdgeTest.foo",
    856    1,
    857    "EdgeTest.foo should not be deleted by deleteBranch('EdgeTest.foo..')"
    858  );
    859  assertPrefNotExists(
    860    "EdgeTest.foo.",
    861    "EdgeTest.foo. should be deleted by deleteBranch('EdgeTest.foo..')"
    862  );
    863  assertPrefExists(
    864    "EdgeTest.foo.bar",
    865    3,
    866    "EdgeTest.foo.bar should not be deleted by deleteBranch('EdgeTest.foo..')"
    867  );
    868  assertPrefNotExists(
    869    "EdgeTest.foo..",
    870    "EdgeTest.foo.. should be deleted by deleteBranch('EdgeTest.foo..')"
    871  );
    872  assertPrefNotExists(
    873    "EdgeTest.foo..baz",
    874    "EdgeTest.foo..baz should be deleted by deleteBranch('EdgeTest.foo..')"
    875  );
    876 
    877  // Clean up
    878  ps.deleteBranch("EdgeTest");
    879 });
    880 
    881 /**
    882 * Tests the reported bug where creating a preference value, clearing it,
    883 * then creating it again as a different type raises an exception.
    884 */
    885 add_task(function test_pref_type_change_after_clear() {
    886  const ps = Services.prefs;
    887  const prefName = "TypeChangeTest.pref";
    888 
    889  // Test 1: Boolean -> Integer
    890  ps.setBoolPref(prefName, true);
    891  Assert.equal(ps.getBoolPref(prefName), true);
    892  Assert.equal(ps.getPrefType(prefName), PREF_BOOL);
    893 
    894  ps.clearUserPref(prefName);
    895  Assert.ok(!ps.prefHasUserValue(prefName));
    896 
    897  // This should work without throwing an exception
    898  ps.setIntPref(prefName, 42);
    899  Assert.equal(ps.getIntPref(prefName), 42);
    900  Assert.equal(ps.getPrefType(prefName), PREF_INT);
    901 
    902  // Test 2: Integer -> String
    903  ps.clearUserPref(prefName);
    904  Assert.ok(!ps.prefHasUserValue(prefName));
    905 
    906  ps.setCharPref(prefName, "test_string");
    907  Assert.equal(ps.getCharPref(prefName), "test_string");
    908  Assert.equal(ps.getPrefType(prefName), PREF_STRING);
    909 
    910  // Test 3: String -> Boolean
    911  ps.clearUserPref(prefName);
    912  Assert.ok(!ps.prefHasUserValue(prefName));
    913 
    914  ps.setBoolPref(prefName, false);
    915  Assert.equal(ps.getBoolPref(prefName), false);
    916  Assert.equal(ps.getPrefType(prefName), PREF_BOOL);
    917 
    918  // Test 4: Test all combinations with prefBranch interface
    919  const pb = ps.getBranch("TypeChangeTest.");
    920  const branchPrefName = "branch_pref";
    921 
    922  // Boolean -> Integer via branch
    923  pb.setBoolPref(branchPrefName, true);
    924  Assert.equal(pb.getBoolPref(branchPrefName), true);
    925  Assert.equal(pb.getPrefType(branchPrefName), PREF_BOOL);
    926 
    927  pb.clearUserPref(branchPrefName);
    928  Assert.ok(!pb.prefHasUserValue(branchPrefName));
    929 
    930  pb.setIntPref(branchPrefName, 123);
    931  Assert.equal(pb.getIntPref(branchPrefName), 123);
    932  Assert.equal(pb.getPrefType(branchPrefName), PREF_INT);
    933 
    934  // Integer -> String via branch
    935  pb.clearUserPref(branchPrefName);
    936  Assert.ok(!pb.prefHasUserValue(branchPrefName));
    937 
    938  pb.setCharPref(branchPrefName, "branch_test");
    939  Assert.equal(pb.getCharPref(branchPrefName), "branch_test");
    940  Assert.equal(pb.getPrefType(branchPrefName), PREF_STRING);
    941 
    942  // String -> Boolean via branch
    943  pb.clearUserPref(branchPrefName);
    944  Assert.ok(!pb.prefHasUserValue(branchPrefName));
    945 
    946  pb.setBoolPref(branchPrefName, true);
    947  Assert.equal(pb.getBoolPref(branchPrefName), true);
    948  Assert.equal(pb.getPrefType(branchPrefName), PREF_BOOL);
    949 
    950  // Clean up
    951  ps.deleteBranch("TypeChangeTest");
    952 });