tor-browser

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

commit 4644daa4db79efa613d714a674634d2a31e07e65
parent 566ab8382ecdfa664704eabb8afbde00ea8e6166
Author: Greg Stoll <gstoll@mozilla.com>
Date:   Fri, 14 Nov 2025 17:03:22 +0000

Bug 849620 - handle deleteBranch() calls with trailing dots and double dots consistently r=beth

Differential Revision: https://phabricator.services.mozilla.com/D270322

Diffstat:
Mmodules/libpref/Preferences.cpp | 8++++----
Mmodules/libpref/test/unit/test_libPrefs.js | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
2 files changed, 164 insertions(+), 32 deletions(-)

diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp @@ -2860,10 +2860,10 @@ nsPrefBranch::DeleteBranch(const char* aStartingAt) { // Collect the list of prefs to remove AutoTArray<const char*, 32> prefNames; for (auto& pref : PrefsIter(HashTable(), gSharedMap)) { - // The first disjunct matches branches: e.g. a branch name "foo.bar." - // matches a name "foo.bar.baz" (but it won't match "foo.barrel.baz"). - // The second disjunct matches leaf nodes: e.g. a branch name "foo.bar." - // matches a name "foo.bar" (by ignoring the trailing '.'). + // Match preferences that start with branchName or equal branchNameNoDot. + // For inputs ending with "..", this matches the node without the trailing + // dot and all children with the double dot prefix. + // For other inputs, this matches both the node itself and all children. if (StringBeginsWith(pref->NameString(), branchName) || pref->NameString() == branchNameNoDot) { prefNames.AppendElement(pref->Name()); diff --git a/modules/libpref/test/unit/test_libPrefs.js b/modules/libpref/test/unit/test_libPrefs.js @@ -570,32 +570,33 @@ add_task(function test_deleteBranch_observers() { ); // Verify the preferences were actually deleted - Assert.throws( - () => ps.getBoolPref("DeleteTest.branch1.bool"), - /NS_ERROR_UNEXPECTED/, - "Deleted boolean pref should throw when accessed" + assertPrefNotExists( + "DeleteTest.branch1.bool", + "Deleted boolean pref should throw when accessed", + "getBoolPref" ); - Assert.throws( - () => ps.getIntPref("DeleteTest.branch1.int"), - /NS_ERROR_UNEXPECTED/, + assertPrefNotExists( + "DeleteTest.branch1.int", "Deleted integer pref should throw when accessed" ); - Assert.throws( - () => ps.getCharPref("DeleteTest.branch1.char"), - /NS_ERROR_UNEXPECTED/, - "Deleted char pref should throw when accessed" + assertPrefNotExists( + "DeleteTest.branch1.char", + "Deleted char pref should throw when accessed", + "getCharPref" ); // Verify other preferences were not affected - Assert.equal( - ps.getBoolPref("DeleteTest.branch2.bool"), + assertPrefExists( + "DeleteTest.branch2.bool", false, - "Unrelated preferences should not be affected" + "Unrelated preferences should not be affected", + "getBoolPref" ); - Assert.equal( - ps.getCharPref("DeleteTest.other"), + assertPrefExists( + "DeleteTest.other", "other", - "Unrelated preferences should not be affected" + "Unrelated preferences should not be affected", + "getCharPref" ); // Clean up observers @@ -684,20 +685,19 @@ add_task(function test_deleteBranch_user_and_default_values() { ); // Verify all preferences are actually deleted - Assert.throws( - () => ps.getBoolPref("MixedTest.pref1"), - /NS_ERROR_UNEXPECTED/, - "Pref with default value should be completely deleted" + assertPrefNotExists( + "MixedTest.pref1", + "Pref with default value should be completely deleted", + "getBoolPref" ); - Assert.throws( - () => ps.getIntPref("MixedTest.pref2"), - /NS_ERROR_UNEXPECTED/, + assertPrefNotExists( + "MixedTest.pref2", "Pref with default value should be completely deleted" ); - Assert.throws( - () => ps.getCharPref("MixedTest.pref3"), - /NS_ERROR_UNEXPECTED/, - "User-only pref should be deleted" + assertPrefNotExists( + "MixedTest.pref3", + "User-only pref should be deleted", + "getCharPref" ); ps.removeObserver("MixedTest.", observer); @@ -745,6 +745,138 @@ add_task(function test_deleteBranch_weak_observers() { }); /** + * Helper function to assert that a preference exists with the expected value. + * @param {string} prefName - The preference name + * @param {*} expectedValue - The expected value + * @param {string} message - The assertion message + * @param {Function} [getter=getIntPref] - The preference getter function (e.g., getIntPref, getBoolPref, getCharPref) + */ +function assertPrefExists( + prefName, + expectedValue, + message, + getter = "getIntPref" +) { + Assert.equal(Services.prefs[getter](prefName), expectedValue, message); +} + +/** + * Helper function to assert that a preference does not exist. + * @param {string} prefName - The preference name + * @param {string} message - The assertion message + * @param {Function} [getter=getIntPref] - The preference getter function (e.g., getIntPref, getBoolPref, getCharPref) + */ +function assertPrefNotExists(prefName, message, getter = "getIntPref") { + Assert.throws( + () => Services.prefs[getter](prefName), + /NS_ERROR_UNEXPECTED/, + message + ); +} + +/** + * Tests specific edge cases for deleteBranch behavior with consecutive dots + */ +add_task(function test_deleteBranch_edge_cases() { + const ps = Services.prefs; + + // Test case 1: deleteBranch("foo") deletes all preferences + ps.setIntPref("EdgeTest.foo", 1); + ps.setIntPref("EdgeTest.foo.", 2); + ps.setIntPref("EdgeTest.foo.bar", 3); + ps.setIntPref("EdgeTest.foo..", 4); + ps.setIntPref("EdgeTest.foo..baz", 5); + + ps.deleteBranch("EdgeTest.foo"); + + assertPrefNotExists( + "EdgeTest.foo", + "EdgeTest.foo should be deleted by deleteBranch('EdgeTest.foo')" + ); + assertPrefNotExists( + "EdgeTest.foo.", + "EdgeTest.foo. should be deleted by deleteBranch('EdgeTest.foo')" + ); + assertPrefNotExists( + "EdgeTest.foo.bar", + "EdgeTest.foo.bar should be deleted by deleteBranch('EdgeTest.foo')" + ); + assertPrefNotExists( + "EdgeTest.foo..", + "EdgeTest.foo.. should be deleted by deleteBranch('EdgeTest.foo')" + ); + assertPrefNotExists( + "EdgeTest.foo..baz", + "EdgeTest.foo..baz should be deleted by deleteBranch('EdgeTest.foo')" + ); + + // Test case 2: deleteBranch("foo.") also deletes all preferences + ps.setIntPref("EdgeTest.foo", 1); + ps.setIntPref("EdgeTest.foo.", 2); + ps.setIntPref("EdgeTest.foo.bar", 3); + ps.setIntPref("EdgeTest.foo..", 4); + ps.setIntPref("EdgeTest.foo..baz", 5); + + ps.deleteBranch("EdgeTest.foo."); + + assertPrefNotExists( + "EdgeTest.foo", + "EdgeTest.foo should be deleted by deleteBranch('EdgeTest.foo.')" + ); + assertPrefNotExists( + "EdgeTest.foo.", + "EdgeTest.foo. should be deleted by deleteBranch('EdgeTest.foo.')" + ); + assertPrefNotExists( + "EdgeTest.foo.bar", + "EdgeTest.foo.bar should be deleted by deleteBranch('EdgeTest.foo.')" + ); + assertPrefNotExists( + "EdgeTest.foo..", + "EdgeTest.foo.. should be deleted by deleteBranch('EdgeTest.foo.')" + ); + assertPrefNotExists( + "EdgeTest.foo..baz", + "EdgeTest.foo..baz should be deleted by deleteBranch('EdgeTest.foo.')" + ); + + // Test case 3: deleteBranch("foo..") deletes only "foo.", "foo..", and "foo..baz" + ps.setIntPref("EdgeTest.foo", 1); + ps.setIntPref("EdgeTest.foo.", 2); + ps.setIntPref("EdgeTest.foo.bar", 3); + ps.setIntPref("EdgeTest.foo..", 4); + ps.setIntPref("EdgeTest.foo..baz", 5); + + ps.deleteBranch("EdgeTest.foo.."); + + assertPrefExists( + "EdgeTest.foo", + 1, + "EdgeTest.foo should not be deleted by deleteBranch('EdgeTest.foo..')" + ); + assertPrefNotExists( + "EdgeTest.foo.", + "EdgeTest.foo. should be deleted by deleteBranch('EdgeTest.foo..')" + ); + assertPrefExists( + "EdgeTest.foo.bar", + 3, + "EdgeTest.foo.bar should not be deleted by deleteBranch('EdgeTest.foo..')" + ); + assertPrefNotExists( + "EdgeTest.foo..", + "EdgeTest.foo.. should be deleted by deleteBranch('EdgeTest.foo..')" + ); + assertPrefNotExists( + "EdgeTest.foo..baz", + "EdgeTest.foo..baz should be deleted by deleteBranch('EdgeTest.foo..')" + ); + + // Clean up + ps.deleteBranch("EdgeTest"); +}); + +/** * Tests the reported bug where creating a preference value, clearing it, * then creating it again as a different type raises an exception. */