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