diff options
Diffstat (limited to '')
34 files changed, 2377 insertions, 0 deletions
diff --git a/modules/libpref/test/unit/data/testParser.js b/modules/libpref/test/unit/data/testParser.js new file mode 100644 index 0000000000..be29430b2a --- /dev/null +++ b/modules/libpref/test/unit/data/testParser.js @@ -0,0 +1,98 @@ +// Note: this file tests only valid syntax (of default pref files, not user +// pref files). See modules/libpref/test/gtest/Parser.cpp for tests of invalid +// syntax. + +# +# comment + # comment £
+//
+// comment + // comment £ +/**/ + /* comment £ */ +/* comment + * and +some
+ # more + // comment */ +// comment /* +# comment /* +/* /* /* /* /* ...no nesting of C-style comments... */ + +// The following four lines have whitespace: \t, \v, \f, \r + + + +
+ +// This comment has the same whitespace:
+# This comment has other stuff: \n \r \t \v \f \r \a \b \? \' \" \\ \@ +/* This comment has more stuff: \x61 \u0061 \u1234 \uXYZ */ + +/*0*/ pref /*1*/ ( /*2*/ "comment1" /*3*/ , /*4*/ true /*5*/ ) /*6*/ ; /*7*/ + +pref # foo +( // foo +"comment2" /* +foo +*/,/*foo*/ +true#foo +)// +; /*7*/ + +pref + ( + "spaced-out" + , + true + ) + ; + +pref("pref", true); +sticky_pref("sticky_pref", true); +user_pref("user_pref", true); +pref("sticky_pref2", true, sticky); +pref("locked_pref", true, locked); +pref("locked_sticky_pref", true, locked, sticky,sticky, + locked, locked, locked); + +pref("bool.true", true); +pref("bool.false", false); + +pref("int.0", 0); +pref("int.1", 1); +pref("int.123", 123); +pref("int.+234", +234); +pref("int.+ 345", + 345); +// Note that both the prefname and value have tabs in them +pref("int.-0", -0); +pref("int.-1", -1); +pref("int.- /* hmm */ 456", - /* hmm */ 456); +pref("int.-\n567", - +567); +pref("int.INT_MAX-1", 2147483646); +pref("int.INT_MAX", 2147483647); +pref("int.INT_MIN+2", -2147483646); +pref("int.INT_MIN+1", -2147483647); +pref("int.INT_MIN", -2147483648); +//pref("int.overflow.max", 2147483648); // overflows to -2147483648 +//pref("int.overflow.min", -2147483649); // overflows to 2147483647 +//pref("int.overflow.other", 4000000000); // overflows to -294967296 +//pref("int.overflow.another", 5000000000000000); // overflows to 937459712 + +pref("string.empty", ""); +pref("string.abc", "abc"); +pref("string.long", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); +pref('string.single-quotes', '"abc"'); +pref("string.double-quotes", "'abc'"); +pref("string.weird-chars", "
"); +pref("string.escapes", "\" \' \\ \n \r"); +pref("string.x-escapes1", "Mozilla0\x4d\x6F\x7a\x69\x6c\x6C\x610"); +pref("string.x-escapes2", "A\x41 A_umlaut\xc4 y_umlaut\xff"); +pref("string.u-escapes1", "A\u0041 A_umlaut\u00c4 y_umlaut\u00ff0"); +pref("string.u-escapes2", "S_acute\u015a y_grave\u1Ef3"); +// CYCLONE is 0x1f300, GRINNING FACE is 0x1f600. We have to represent them via +// surrogate pairs. +pref("string.u-surrogates", + "cyclone\uD83C\uDF00 grinning_face\uD83D\uDE00"); + diff --git a/modules/libpref/test/unit/data/testPref.js b/modules/libpref/test/unit/data/testPref.js new file mode 100644 index 0000000000..0864e7ce8b --- /dev/null +++ b/modules/libpref/test/unit/data/testPref.js @@ -0,0 +1,6 @@ +user_pref("testPref.bool1", true); +user_pref("testPref.bool2", false); +user_pref("testPref.int1", 23); +user_pref("testPref.int2", -1236); +user_pref("testPref.char1", "_testPref"); +user_pref("testPref.char2", "älskar");
\ No newline at end of file diff --git a/modules/libpref/test/unit/data/testPrefLocked.js b/modules/libpref/test/unit/data/testPrefLocked.js new file mode 100644 index 0000000000..001b65122b --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefLocked.js @@ -0,0 +1,2 @@ +pref("testPref.unlocked.int", 333); +pref("testPref.locked.int", 444, locked); diff --git a/modules/libpref/test/unit/data/testPrefLockedUser.js b/modules/libpref/test/unit/data/testPrefLockedUser.js new file mode 100644 index 0000000000..4da40b7abc --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefLockedUser.js @@ -0,0 +1,3 @@ +// testPrefLocked.js defined this pref as a locked pref. +// Changing a locked pref has no effect. +user_pref("testPref.locked.int", 555); diff --git a/modules/libpref/test/unit/data/testPrefSticky.js b/modules/libpref/test/unit/data/testPrefSticky.js new file mode 100644 index 0000000000..ef1a02adfd --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefSticky.js @@ -0,0 +1,2 @@ +pref("testPref.unsticky.bool", true); +pref("testPref.sticky.bool", false, sticky); diff --git a/modules/libpref/test/unit/data/testPrefStickyUser.js b/modules/libpref/test/unit/data/testPrefStickyUser.js new file mode 100644 index 0000000000..d929c670ad --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefStickyUser.js @@ -0,0 +1,5 @@ +// testPrefSticky.js defined this pref as sticky. Once a sticky +// pref has been changed, it's written as a user_pref(). +// So this test file reflects that scenario. +// Note the default in testPrefSticky.js is also false. +user_pref("testPref.sticky.bool", false); diff --git a/modules/libpref/test/unit/data/testPrefUTF8.js b/modules/libpref/test/unit/data/testPrefUTF8.js new file mode 100644 index 0000000000..a409c4eb72 --- /dev/null +++ b/modules/libpref/test/unit/data/testPrefUTF8.js @@ -0,0 +1,6 @@ +user_pref("testPref.bool1", true); +user_pref("testPref.bool2", false); +user_pref("testPref.int1", 23); +user_pref("testPref.int2", -1236); +user_pref("testPref.char1", "_testPref"); +user_pref("testPref.char2", "älskar"); diff --git a/modules/libpref/test/unit/extdata/testExt.js b/modules/libpref/test/unit/extdata/testExt.js new file mode 100644 index 0000000000..17c6849692 --- /dev/null +++ b/modules/libpref/test/unit/extdata/testExt.js @@ -0,0 +1,2 @@ +pref("testPref.bool2", true); +pref("testExtPref.bool", true); diff --git a/modules/libpref/test/unit/head_libPrefs.js b/modules/libpref/test/unit/head_libPrefs.js new file mode 100644 index 0000000000..ef7044a641 --- /dev/null +++ b/modules/libpref/test/unit/head_libPrefs.js @@ -0,0 +1,37 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const NS_APP_USER_PROFILE_50_DIR = "ProfD"; + +function do_check_throws(f, result, stack) { + if (!stack) { + stack = Components.stack.caller; + } + + try { + f(); + } catch (exc) { + equal(exc.result, result, "Correct exception was thrown"); + return; + } + ok(false, "expected result " + result + ", none thrown"); +} + +// Register current test directory as provider for the profile directory. +var provider = { + getFile(prop, persistent) { + persistent.value = true; + if (prop == NS_APP_USER_PROFILE_50_DIR) { + return Services.dirsvc.get("CurProcD", Ci.nsIFile); + } + throw Components.Exception( + "Tried to get test directory '" + prop + "'", + Cr.NS_ERROR_FAILURE + ); + }, + QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]), +}; +Services.dirsvc + .QueryInterface(Ci.nsIDirectoryService) + .registerProvider(provider); diff --git a/modules/libpref/test/unit/test_bug1354613.js b/modules/libpref/test/unit/test_bug1354613.js new file mode 100644 index 0000000000..e609c6805a --- /dev/null +++ b/modules/libpref/test/unit/test_bug1354613.js @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + const BRANCH = "foo."; + const PREF_NAME = "testPref"; + const FULL_PREF_NAME = BRANCH + PREF_NAME; + + const FLOAT = 9.674; + const FUDGE = 0.001; + + let prefs = Services.prefs.getDefaultBranch(null); + + /* Test with a non-default branch */ + prefs.setCharPref(FULL_PREF_NAME, FLOAT); + let pb = Services.prefs.getBranch(BRANCH); + + let floatPref = pb.getFloatPref(PREF_NAME); + Assert.ok(FLOAT + FUDGE >= floatPref); + Assert.ok(FLOAT - FUDGE <= floatPref); +} diff --git a/modules/libpref/test/unit/test_bug345529.js b/modules/libpref/test/unit/test_bug345529.js new file mode 100644 index 0000000000..05fce35f57 --- /dev/null +++ b/modules/libpref/test/unit/test_bug345529.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +// Regression test for bug 345529 - crash removing an observer during an +// nsPref:changed notification. +function run_test() { + const PREF_NAME = "testPref"; + + var observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + observe: function observe(aSubject, aTopic, aState) { + Services.prefs.removeObserver(PREF_NAME, observer); + }, + }; + Services.prefs.addObserver(PREF_NAME, observer); + + Services.prefs.setCharPref(PREF_NAME, "test0"); + // This second call isn't needed on a clean profile: it makes sure + // the observer gets called even if the pref already had the value + // "test0" before this test. + Services.prefs.setCharPref(PREF_NAME, "test1"); + + Assert.ok(true); +} diff --git a/modules/libpref/test/unit/test_bug506224.js b/modules/libpref/test/unit/test_bug506224.js new file mode 100644 index 0000000000..033c5acc6e --- /dev/null +++ b/modules/libpref/test/unit/test_bug506224.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + const PREF_NAME = "testPref"; + + var prefs = Services.prefs.getDefaultBranch(null); + var userprefs = Services.prefs.getBranch(null); + + prefs.setCharPref(PREF_NAME, "test0"); + prefs.lockPref(PREF_NAME); + Assert.equal("test0", userprefs.getCharPref(PREF_NAME)); + Assert.equal(false, userprefs.prefHasUserValue(PREF_NAME)); + + var file = do_get_profile(); + file.append("prefs.js"); + Services.prefs.savePrefFile(file); + + prefs.unlockPref(PREF_NAME); + prefs.setCharPref(PREF_NAME, "test1"); + Services.prefs.readUserPrefsFromFile(file); + + Assert.equal("test1", userprefs.getCharPref(PREF_NAME)); + Assert.equal(false, userprefs.prefHasUserValue(PREF_NAME)); +} diff --git a/modules/libpref/test/unit/test_bug577950.js b/modules/libpref/test/unit/test_bug577950.js new file mode 100644 index 0000000000..f9106a349f --- /dev/null +++ b/modules/libpref/test/unit/test_bug577950.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + observe: function observe(aSubject, aTopic, aState) { + // Don't do anything. + }, + }; + + /* Set the same pref twice. This shouldn't leak. */ + Services.prefs.addObserver("UserPref.nonexistent.setIntPref", observer); + Services.prefs.addObserver("UserPref.nonexistent.setIntPref", observer); +} diff --git a/modules/libpref/test/unit/test_bug790374.js b/modules/libpref/test/unit/test_bug790374.js new file mode 100644 index 0000000000..d4271a7ff7 --- /dev/null +++ b/modules/libpref/test/unit/test_bug790374.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + const PREF_NAME = "testPref"; + + var prefs = Services.prefs.getDefaultBranch(null); + var userprefs = Services.prefs.getBranch(null); + + /* First, test to make sure we can parse a float from a string properly. */ + prefs.setCharPref(PREF_NAME, "9.674"); + prefs.lockPref(PREF_NAME); + var myFloat = 9.674; + var fudge = 0.001; + var floatPref = userprefs.getFloatPref(PREF_NAME); + Assert.ok(myFloat + fudge >= floatPref); + Assert.ok(myFloat - fudge <= floatPref); + + /* Now test some failure conditions. */ + prefs.unlockPref(PREF_NAME); + prefs.setCharPref(PREF_NAME, ""); + prefs.lockPref(PREF_NAME); + do_check_throws(function () { + userprefs.getFloatPref(PREF_NAME); + }, Cr.NS_ERROR_ILLEGAL_VALUE); + + prefs.unlockPref(PREF_NAME); + prefs.setCharPref(PREF_NAME, "18.0a1"); + prefs.lockPref(PREF_NAME); + + do_check_throws(function () { + userprefs.getFloatPref(PREF_NAME); + }, Cr.NS_ERROR_ILLEGAL_VALUE); + + prefs.unlockPref(PREF_NAME); + prefs.setCharPref(PREF_NAME, "09.25.2012"); + prefs.lockPref(PREF_NAME); + + do_check_throws(function () { + userprefs.getFloatPref(PREF_NAME); + }, Cr.NS_ERROR_ILLEGAL_VALUE); + + prefs.unlockPref(PREF_NAME); + prefs.setCharPref(PREF_NAME, "aString"); + prefs.lockPref(PREF_NAME); + + do_check_throws(function () { + userprefs.getFloatPref(PREF_NAME); + }, Cr.NS_ERROR_ILLEGAL_VALUE); +} diff --git a/modules/libpref/test/unit/test_changeType.js b/modules/libpref/test/unit/test_changeType.js new file mode 100644 index 0000000000..94f3a84f13 --- /dev/null +++ b/modules/libpref/test/unit/test_changeType.js @@ -0,0 +1,164 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Tests for changing the type of a preference (bug 985998) */ + +function run_test() { + var ps = Services.prefs; + + let defaultBranch = ps.getDefaultBranch(""); + let userBranch = ps.getBranch(""); + + // Prefs that only have a default value -- we can't change their type. + defaultBranch.setBoolPref("TypeTest.default.bool", true); + defaultBranch.setIntPref("TypeTest.default.int", 23); + defaultBranch.setCharPref("TypeTest.default.char", "hey"); + + Assert.equal(userBranch.getBoolPref("TypeTest.default.bool"), true); + Assert.equal(userBranch.getIntPref("TypeTest.default.int"), 23); + Assert.equal(userBranch.getCharPref("TypeTest.default.char"), "hey"); + + // Prefs that only have a user value -- we can change their type, but only + // when we set the user value. + userBranch.setBoolPref("TypeTest.user.bool", false); + userBranch.setIntPref("TypeTest.user.int", 24); + userBranch.setCharPref("TypeTest.user.char", "hi"); + + Assert.equal(userBranch.getBoolPref("TypeTest.user.bool"), false); + Assert.equal(userBranch.getIntPref("TypeTest.user.int"), 24); + Assert.equal(userBranch.getCharPref("TypeTest.user.char"), "hi"); + + // Prefs that have both a default and a user value -- we can't change their + // type. + defaultBranch.setBoolPref("TypeTest.both.bool", true); + userBranch.setBoolPref("TypeTest.both.bool", false); + defaultBranch.setIntPref("TypeTest.both.int", 25); + userBranch.setIntPref("TypeTest.both.int", 26); + defaultBranch.setCharPref("TypeTest.both.char", "yo"); + userBranch.setCharPref("TypeTest.both.char", "ya"); + + Assert.equal(userBranch.getBoolPref("TypeTest.both.bool"), false); + Assert.equal(userBranch.getIntPref("TypeTest.both.int"), 26); + Assert.equal(userBranch.getCharPref("TypeTest.both.char"), "ya"); + + // We only have a default value, and we try to set a default value of a + // different type --> fails. + do_check_throws(function () { + defaultBranch.setCharPref("TypeTest.default.bool", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setIntPref("TypeTest.default.bool", 5); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setCharPref("TypeTest.default.int", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setBoolPref("TypeTest.default.int", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setBoolPref("TypeTest.default.char", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setIntPref("TypeTest.default.char", 6); + }, Cr.NS_ERROR_UNEXPECTED); + + // We only have a default value, and we try to set a user value of a + // different type --> fails. + do_check_throws(function () { + userBranch.setCharPref("TypeTest.default.bool", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setIntPref("TypeTest.default.bool", 5); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setCharPref("TypeTest.default.int", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setBoolPref("TypeTest.default.int", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setBoolPref("TypeTest.default.char", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setIntPref("TypeTest.default.char", 6); + }, Cr.NS_ERROR_UNEXPECTED); + + // We only have a user value, and we try to set a default value of a + // different type --> fails. + do_check_throws(function () { + defaultBranch.setCharPref("TypeTest.user.bool", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setIntPref("TypeTest.user.bool", 5); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setCharPref("TypeTest.user.int", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setBoolPref("TypeTest.user.int", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setBoolPref("TypeTest.user.char", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setIntPref("TypeTest.user.char", 6); + }, Cr.NS_ERROR_UNEXPECTED); + + // We only have a user value, and we try to set a user value of a + // different type --> SUCCEEDS. + userBranch.setCharPref("TypeTest.user.bool", "boo"); + Assert.equal(userBranch.getCharPref("TypeTest.user.bool"), "boo"); + userBranch.setIntPref("TypeTest.user.bool", 5); + Assert.equal(userBranch.getIntPref("TypeTest.user.bool"), 5); + userBranch.setCharPref("TypeTest.user.int", "boo"); + Assert.equal(userBranch.getCharPref("TypeTest.user.int"), "boo"); + userBranch.setBoolPref("TypeTest.user.int", true); + Assert.equal(userBranch.getBoolPref("TypeTest.user.int"), true); + userBranch.setBoolPref("TypeTest.user.char", true); + Assert.equal(userBranch.getBoolPref("TypeTest.user.char"), true); + userBranch.setIntPref("TypeTest.user.char", 6); + Assert.equal(userBranch.getIntPref("TypeTest.user.char"), 6); + + // We have both a default value and user value, and we try to set a default + // value of a different type --> fails. + do_check_throws(function () { + defaultBranch.setCharPref("TypeTest.both.bool", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setIntPref("TypeTest.both.bool", 5); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setCharPref("TypeTest.both.int", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setBoolPref("TypeTest.both.int", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setBoolPref("TypeTest.both.char", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + defaultBranch.setIntPref("TypeTest.both.char", 6); + }, Cr.NS_ERROR_UNEXPECTED); + + // We have both a default value and user value, and we try to set a user + // value of a different type --> fails. + do_check_throws(function () { + userBranch.setCharPref("TypeTest.both.bool", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setIntPref("TypeTest.both.bool", 5); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setCharPref("TypeTest.both.int", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setBoolPref("TypeTest.both.int", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setBoolPref("TypeTest.both.char", true); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + userBranch.setIntPref("TypeTest.both.char", 6); + }, Cr.NS_ERROR_UNEXPECTED); +} diff --git a/modules/libpref/test/unit/test_defaultValues.js b/modules/libpref/test/unit/test_defaultValues.js new file mode 100644 index 0000000000..905675a1cf --- /dev/null +++ b/modules/libpref/test/unit/test_defaultValues.js @@ -0,0 +1,59 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Tests for providing a default value to get{Bool,Char,Float,Int}Pref */ + +function run_test() { + const ps = Services.prefs; + let prefName = "test.default.values.bool"; + do_check_throws(function () { + ps.getBoolPref(prefName); + }, Cr.NS_ERROR_UNEXPECTED); + strictEqual(ps.getBoolPref(prefName, false), false); + strictEqual(ps.getBoolPref(prefName, true), true); + ps.setBoolPref(prefName, true); + strictEqual(ps.getBoolPref(prefName), true); + strictEqual(ps.getBoolPref(prefName, false), true); + strictEqual(ps.getBoolPref(prefName, true), true); + + prefName = "test.default.values.char"; + do_check_throws(function () { + ps.getCharPref(prefName); + }, Cr.NS_ERROR_UNEXPECTED); + strictEqual(ps.getCharPref(prefName, ""), ""); + strictEqual(ps.getCharPref(prefName, "string"), "string"); + ps.setCharPref(prefName, "foo"); + strictEqual(ps.getCharPref(prefName), "foo"); + strictEqual(ps.getCharPref(prefName, "string"), "foo"); + + prefName = "test.default.values.string"; + do_check_throws(function () { + ps.getCharPref(prefName); + }, Cr.NS_ERROR_UNEXPECTED); + strictEqual(ps.getStringPref(prefName, ""), ""); + strictEqual(ps.getStringPref(prefName, "éèçà ê€"), "éèçà ê€"); + ps.setStringPref(prefName, "éèçà ê€"); + strictEqual(ps.getStringPref(prefName), "éèçà ê€"); + strictEqual(ps.getStringPref(prefName, "string"), "éèçà ê€"); + + prefName = "test.default.values.float"; + do_check_throws(function () { + ps.getFloatPref(prefName); + }, Cr.NS_ERROR_UNEXPECTED); + strictEqual(ps.getFloatPref(prefName, 3.5), 3.5); + strictEqual(ps.getFloatPref(prefName, 0), 0); + ps.setCharPref(prefName, 1.75); + strictEqual(ps.getFloatPref(prefName), 1.75); + strictEqual(ps.getFloatPref(prefName, 3.5), 1.75); + + prefName = "test.default.values.int"; + do_check_throws(function () { + ps.getIntPref(prefName); + }, Cr.NS_ERROR_UNEXPECTED); + strictEqual(ps.getIntPref(prefName, 3), 3); + strictEqual(ps.getIntPref(prefName, 0), 0); + ps.setIntPref(prefName, 42); + strictEqual(ps.getIntPref(prefName), 42); + strictEqual(ps.getIntPref(prefName, 3), 42); +} diff --git a/modules/libpref/test/unit/test_dirtyPrefs.js b/modules/libpref/test/unit/test_dirtyPrefs.js new file mode 100644 index 0000000000..e107bef363 --- /dev/null +++ b/modules/libpref/test/unit/test_dirtyPrefs.js @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Tests for handling of the preferences 'dirty' flag (bug 985998) */ + +function run_test() { + const ps = Services.prefs; + + let defaultBranch = ps.getDefaultBranch(""); + let userBranch = ps.getBranch(""); + + let prefFile = do_get_profile(); + prefFile.append("prefs.js"); + + //* *************************************************************************// + // prefs are not dirty after a write + ps.savePrefFile(null); + Assert.ok(!ps.dirty); + + // set a new a user value, we should become dirty + userBranch.setBoolPref("DirtyTest.new.bool", true); + Assert.ok(ps.dirty); + ps.savePrefFile(null); + // Overwrite a pref with the same value => not dirty + userBranch.setBoolPref("DirtyTest.new.bool", true); + Assert.ok(!ps.dirty); + + // Repeat for the other two types + userBranch.setIntPref("DirtyTest.new.int", 1); + Assert.ok(ps.dirty); + ps.savePrefFile(null); + // Overwrite a pref with the same value => not dirty + userBranch.setIntPref("DirtyTest.new.int", 1); + Assert.ok(!ps.dirty); + + userBranch.setCharPref("DirtyTest.new.char", "oop"); + Assert.ok(ps.dirty); + ps.savePrefFile(null); + // Overwrite a pref with the same value => not dirty + userBranch.setCharPref("DirtyTest.new.char", "oop"); + Assert.ok(!ps.dirty); + + // change *type* of a user value -> dirty + userBranch.setBoolPref("DirtyTest.new.char", false); + Assert.ok(ps.dirty); + ps.savePrefFile(null); + + // Set a default pref => not dirty (defaults don't go into prefs.js) + defaultBranch.setBoolPref("DirtyTest.existing.bool", true); + Assert.ok(!ps.dirty); + // Fail to change type of a pref with default value -> not dirty + do_check_throws(function () { + userBranch.setCharPref("DirtyTest.existing.bool", "boo"); + }, Cr.NS_ERROR_UNEXPECTED); + Assert.ok(!ps.dirty); + + // Set user value same as default, not dirty + userBranch.setBoolPref("DirtyTest.existing.bool", true); + Assert.ok(!ps.dirty); + // User value different from default, dirty + userBranch.setBoolPref("DirtyTest.existing.bool", false); + Assert.ok(ps.dirty); + ps.savePrefFile(null); + // Back to default value, dirty again + userBranch.setBoolPref("DirtyTest.existing.bool", true); + Assert.ok(ps.dirty); + ps.savePrefFile(null); +} diff --git a/modules/libpref/test/unit/test_libPrefs.js b/modules/libpref/test/unit/test_libPrefs.js new file mode 100644 index 0000000000..93651568be --- /dev/null +++ b/modules/libpref/test/unit/test_libPrefs.js @@ -0,0 +1,450 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// It is necessary to manually disable `xpc::IsInAutomation` since +// `resetPrefs` will flip the preference to re-enable `once`-synced +// preference change assertions, and also change the value of those +// preferences. +Services.prefs.setBoolPref( + "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", + false +); + +const PREF_INVALID = 0; +const PREF_BOOL = 128; +const PREF_INT = 64; +const PREF_STRING = 32; + +const MAX_PREF_LENGTH = 1 * 1024 * 1024; + +function makeList(a) { + var o = {}; + for (var i = 0; i < a.length; i++) { + o[a[i]] = ""; + } + return o; +} + +function run_test() { + const ps = Services.prefs; + + //* *************************************************************************// + // Nullsafety + + do_check_throws(function () { + ps.getPrefType(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.getBoolPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.setBoolPref(null, false); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.getIntPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.setIntPref(null, 0); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.getCharPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.setCharPref(null, null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.getStringPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.setStringPref(null, null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.clearUserPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.prefHasUserValue(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.lockPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.prefIsLocked(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.unlockPref(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.deleteBranch(null); + }, Cr.NS_ERROR_INVALID_ARG); + do_check_throws(function () { + ps.getChildList(null); + }, Cr.NS_ERROR_INVALID_ARG); + + //* *************************************************************************// + // Nonexisting user preferences + + Assert.equal(ps.prefHasUserValue("UserPref.nonexistent.hasUserValue"), false); + ps.clearUserPref("UserPref.nonexistent.clearUserPref"); // shouldn't throw + Assert.equal( + ps.getPrefType("UserPref.nonexistent.getPrefType"), + PREF_INVALID + ); + Assert.equal(ps.root, ""); + + // bool... + do_check_throws(function () { + ps.getBoolPref("UserPref.nonexistent.getBoolPref"); + }, Cr.NS_ERROR_UNEXPECTED); + ps.setBoolPref("UserPref.nonexistent.setBoolPref", false); + Assert.equal(ps.getBoolPref("UserPref.nonexistent.setBoolPref"), false); + + // int... + do_check_throws(function () { + ps.getIntPref("UserPref.nonexistent.getIntPref"); + }, Cr.NS_ERROR_UNEXPECTED); + ps.setIntPref("UserPref.nonexistent.setIntPref", 5); + Assert.equal(ps.getIntPref("UserPref.nonexistent.setIntPref"), 5); + + // char + do_check_throws(function () { + ps.getCharPref("UserPref.nonexistent.getCharPref"); + }, Cr.NS_ERROR_UNEXPECTED); + ps.setCharPref("UserPref.nonexistent.setCharPref", "_test"); + Assert.equal(ps.getCharPref("UserPref.nonexistent.setCharPref"), "_test"); + + //* *************************************************************************// + // Existing user Prefs and data integrity test (round-trip match) + + ps.setBoolPref("UserPref.existing.bool", true); + ps.setIntPref("UserPref.existing.int", 23); + ps.setCharPref("UserPref.existing.char", "hey"); + + // getPref should return the pref value + Assert.equal(ps.getBoolPref("UserPref.existing.bool"), true); + Assert.equal(ps.getIntPref("UserPref.existing.int"), 23); + Assert.equal(ps.getCharPref("UserPref.existing.char"), "hey"); + + // setPref should not complain and should change the value of the pref + ps.setBoolPref("UserPref.existing.bool", false); + Assert.equal(ps.getBoolPref("UserPref.existing.bool"), false); + ps.setIntPref("UserPref.existing.int", 24); + Assert.equal(ps.getIntPref("UserPref.existing.int"), 24); + ps.setCharPref("UserPref.existing.char", "hej då!"); + Assert.equal(ps.getCharPref("UserPref.existing.char"), "hej då!"); + + // prefHasUserValue should return true now + Assert.ok(ps.prefHasUserValue("UserPref.existing.bool")); + Assert.ok(ps.prefHasUserValue("UserPref.existing.int")); + Assert.ok(ps.prefHasUserValue("UserPref.existing.char")); + + // clearUserPref should remove the pref + ps.clearUserPref("UserPref.existing.bool"); + Assert.ok(!ps.prefHasUserValue("UserPref.existing.bool")); + ps.clearUserPref("UserPref.existing.int"); + Assert.ok(!ps.prefHasUserValue("UserPref.existing.int")); + ps.clearUserPref("UserPref.existing.char"); + Assert.ok(!ps.prefHasUserValue("UserPref.existing.char")); + + //* *************************************************************************// + // Large value test + + let largeStr = new Array(MAX_PREF_LENGTH + 1).join("x"); + ps.setCharPref("UserPref.large.char", largeStr); + largeStr += "x"; + do_check_throws(function () { + ps.setCharPref("UserPref.large.char", largeStr); + }, Cr.NS_ERROR_ILLEGAL_VALUE); + + //* *************************************************************************// + // getPrefType test + + // bool... + ps.setBoolPref("UserPref.getPrefType.bool", true); + Assert.equal(ps.getPrefType("UserPref.getPrefType.bool"), PREF_BOOL); + + // int... + ps.setIntPref("UserPref.getPrefType.int", -234); + Assert.equal(ps.getPrefType("UserPref.getPrefType.int"), PREF_INT); + + // char... + ps.setCharPref("UserPref.getPrefType.char", "testing1..2"); + Assert.equal(ps.getPrefType("UserPref.getPrefType.char"), PREF_STRING); + + //* *************************************************************************// + // getBranch tests + + Assert.equal(ps.root, ""); + + // bool ... + ps.setBoolPref("UserPref.root.boolPref", true); + let pb_1 = ps.getBranch("UserPref.root."); + Assert.equal(pb_1.getBoolPref("boolPref"), true); + let pb_2 = ps.getBranch("UserPref.root.boolPref"); + Assert.equal(pb_2.getBoolPref(""), true); + pb_2.setBoolPref(".anotherPref", false); + let pb_3 = ps.getBranch("UserPref.root.boolPre"); + Assert.equal(pb_3.getBoolPref("f.anotherPref"), false); + + // int ... + ps.setIntPref("UserPref.root.intPref", 23); + pb_1 = ps.getBranch("UserPref.root."); + Assert.equal(pb_1.getIntPref("intPref"), 23); + pb_2 = ps.getBranch("UserPref.root.intPref"); + Assert.equal(pb_2.getIntPref(""), 23); + pb_2.setIntPref(".anotherPref", 69); + pb_3 = ps.getBranch("UserPref.root.intPre"); + Assert.equal(pb_3.getIntPref("f.anotherPref"), 69); + + // char... + ps.setCharPref("UserPref.root.charPref", "_char"); + pb_1 = ps.getBranch("UserPref.root."); + Assert.equal(pb_1.getCharPref("charPref"), "_char"); + pb_2 = ps.getBranch("UserPref.root.charPref"); + Assert.equal(pb_2.getCharPref(""), "_char"); + pb_2.setCharPref(".anotherPref", "_another"); + pb_3 = ps.getBranch("UserPref.root.charPre"); + Assert.equal(pb_3.getCharPref("f.anotherPref"), "_another"); + + //* *************************************************************************// + // getChildlist tests + + // get an already set prefBranch + let pb1 = ps.getBranch("UserPref.root."); + let prefList = pb1.getChildList(""); + Assert.equal(prefList.length, 6); + + // check for specific prefs in the array : the order is not important + Assert.ok("boolPref" in makeList(prefList)); + Assert.ok("intPref" in makeList(prefList)); + Assert.ok("charPref" in makeList(prefList)); + Assert.ok("boolPref.anotherPref" in makeList(prefList)); + Assert.ok("intPref.anotherPref" in makeList(prefList)); + Assert.ok("charPref.anotherPref" in makeList(prefList)); + + //* *************************************************************************// + // Default branch tests + + // bool... + pb1 = ps.getDefaultBranch(""); + pb1.setBoolPref("DefaultPref.bool", true); + Assert.equal(pb1.getBoolPref("DefaultPref.bool"), true); + Assert.ok(!pb1.prefHasUserValue("DefaultPref.bool")); + ps.setBoolPref("DefaultPref.bool", false); + Assert.ok(pb1.prefHasUserValue("DefaultPref.bool")); + Assert.equal(ps.getBoolPref("DefaultPref.bool"), false); + + // int... + pb1 = ps.getDefaultBranch(""); + pb1.setIntPref("DefaultPref.int", 100); + Assert.equal(pb1.getIntPref("DefaultPref.int"), 100); + Assert.ok(!pb1.prefHasUserValue("DefaultPref.int")); + ps.setIntPref("DefaultPref.int", 50); + Assert.ok(pb1.prefHasUserValue("DefaultPref.int")); + Assert.equal(ps.getIntPref("DefaultPref.int"), 50); + + // char... + pb1 = ps.getDefaultBranch(""); + pb1.setCharPref("DefaultPref.char", "_default"); + Assert.equal(pb1.getCharPref("DefaultPref.char"), "_default"); + Assert.ok(!pb1.prefHasUserValue("DefaultPref.char")); + ps.setCharPref("DefaultPref.char", "_user"); + Assert.ok(pb1.prefHasUserValue("DefaultPref.char")); + Assert.equal(ps.getCharPref("DefaultPref.char"), "_user"); + + //* *************************************************************************// + // pref Locking/Unlocking tests + + // locking and unlocking a nonexistent pref should throw + do_check_throws(function () { + ps.lockPref("DefaultPref.nonexistent"); + }, Cr.NS_ERROR_ILLEGAL_VALUE); + do_check_throws(function () { + ps.unlockPref("DefaultPref.nonexistent"); + }, Cr.NS_ERROR_ILLEGAL_VALUE); + + // getting a locked pref branch should return the "default" value + Assert.ok(!ps.prefIsLocked("DefaultPref.char")); + ps.lockPref("DefaultPref.char"); + Assert.equal(ps.getCharPref("DefaultPref.char"), "_default"); + Assert.ok(ps.prefIsLocked("DefaultPref.char")); + + // getting an unlocked pref branch should return the "user" value + ps.unlockPref("DefaultPref.char"); + Assert.equal(ps.getCharPref("DefaultPref.char"), "_user"); + Assert.ok(!ps.prefIsLocked("DefaultPref.char")); + + // setting the "default" value to a user pref branch should + // make prefHasUserValue return false (documented behavior) + ps.setCharPref("DefaultPref.char", "_default"); + Assert.ok(!pb1.prefHasUserValue("DefaultPref.char")); + + // unlocking and locking multiple times shouldn't throw + ps.unlockPref("DefaultPref.char"); + ps.lockPref("DefaultPref.char"); + ps.lockPref("DefaultPref.char"); + + //* *************************************************************************// + // resetBranch test + + // NOT IMPLEMENTED YET in module/libpref. So we're not testing ! + // uncomment the following if resetBranch ever gets implemented. + /* ps.resetBranch("DefaultPref"); + do_check_eq(ps.getBoolPref("DefaultPref.bool"), true); + do_check_eq(ps.getIntPref("DefaultPref.int"), 100); + do_check_eq(ps.getCharPref("DefaultPref.char"), "_default");*/ + + //* *************************************************************************// + // deleteBranch tests + + // TODO : Really, this should throw!, by documentation. + // do_check_throws(function() { + // ps.deleteBranch("UserPref.nonexistent.deleteBranch");}, Cr.NS_ERROR_UNEXPECTED); + + ps.deleteBranch("DefaultPref"); + let pb = ps.getBranch("DefaultPref"); + pb1 = ps.getDefaultBranch("DefaultPref"); + + // getting prefs on deleted user branches should throw + do_check_throws(function () { + pb.getBoolPref("DefaultPref.bool"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + pb.getIntPref("DefaultPref.int"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + pb.getCharPref("DefaultPref.char"); + }, Cr.NS_ERROR_UNEXPECTED); + + // getting prefs on deleted default branches should throw + do_check_throws(function () { + pb1.getBoolPref("DefaultPref.bool"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + pb1.getIntPref("DefaultPref.int"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + pb1.getCharPref("DefaultPref.char"); + }, Cr.NS_ERROR_UNEXPECTED); + + //* *************************************************************************// + // savePrefFile & readPrefFile tests + + // set some prefs + ps.setBoolPref("ReadPref.bool", true); + ps.setIntPref("ReadPref.int", 230); + ps.setCharPref("ReadPref.char", "hello"); + + // save those prefs in a file + let savePrefFile = do_get_cwd(); + savePrefFile.append("data"); + savePrefFile.append("savePref.js"); + + if (savePrefFile.exists()) { + savePrefFile.remove(false); + } + savePrefFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + ps.savePrefFile(savePrefFile); + ps.resetPrefs(); + + // load a preexisting pref file + let prefFile = do_get_file("data/testPref.js"); + ps.readUserPrefsFromFile(prefFile); + + // former prefs should have been replaced/lost + do_check_throws(function () { + pb.getBoolPref("ReadPref.bool"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + pb.getIntPref("ReadPref.int"); + }, Cr.NS_ERROR_UNEXPECTED); + do_check_throws(function () { + pb.getCharPref("ReadPref.char"); + }, Cr.NS_ERROR_UNEXPECTED); + + // loaded prefs should read ok. + pb = ps.getBranch("testPref."); + Assert.equal(pb.getBoolPref("bool1"), true); + Assert.equal(pb.getBoolPref("bool2"), false); + Assert.equal(pb.getIntPref("int1"), 23); + Assert.equal(pb.getIntPref("int2"), -1236); + Assert.equal(pb.getCharPref("char1"), "_testPref"); + Assert.equal(pb.getCharPref("char2"), "älskar"); + + // loading our former savePrefFile should allow us to read former prefs + + // Hack alert: on Windows nsLocalFile caches the size of savePrefFile from + // the .create() call above as 0. We call .exists() to reset the cache. + savePrefFile.exists(); + + ps.readUserPrefsFromFile(savePrefFile); + // cleanup the file now we don't need it + savePrefFile.remove(false); + Assert.equal(ps.getBoolPref("ReadPref.bool"), true); + Assert.equal(ps.getIntPref("ReadPref.int"), 230); + Assert.equal(ps.getCharPref("ReadPref.char"), "hello"); + + // ... and still be able to access "prior-to-readUserPrefs" preferences + Assert.equal(pb.getBoolPref("bool1"), true); + Assert.equal(pb.getBoolPref("bool2"), false); + Assert.equal(pb.getIntPref("int1"), 23); + + //* *************************************************************************// + // preference Observers + + class PrefObserver { + /** + * Creates and registers a pref observer. + * + * @param prefBranch The preference branch instance to observe. + * @param expectedName The pref name we expect to receive. + * @param expectedValue The int pref value we expect to receive. + */ + constructor(prefBranch, expectedName, expectedValue) { + this.pb = prefBranch; + this.name = expectedName; + this.value = expectedValue; + + prefBranch.addObserver(expectedName, this); + } + + QueryInterface(aIID) { + if (aIID.equals(Ci.nsIObserver) || aIID.equals(Ci.nsISupports)) { + return this; + } + throw Components.Exception("", Cr.NS_NOINTERFACE); + } + + observe(aSubject, aTopic, aState) { + Assert.equal(aTopic, "nsPref:changed"); + Assert.equal(aState, this.name); + Assert.equal(this.pb.getIntPref(aState), this.value); + pb.removeObserver(aState, this); + + // notification received, we may go on... + do_test_finished(); + } + } + + // Indicate that we'll have 3 more async tests pending so that they all + // actually get a chance to run. + do_test_pending(); + do_test_pending(); + do_test_pending(); + + let observer = new PrefObserver(ps, "ReadPref.int", 76); + ps.setIntPref("ReadPref.int", 76); + + // removed observer should not fire + ps.removeObserver("ReadPref.int", observer); + ps.setIntPref("ReadPref.int", 32); + + // let's test observers once more with a non-root prefbranch + pb = ps.getBranch("ReadPref."); + observer = new PrefObserver(pb, "int", 76); + ps.setIntPref("ReadPref.int", 76); + + // Let's try that again with different pref. + observer = new PrefObserver(pb, "another_int", 76); + ps.setIntPref("ReadPref.another_int", 76); +} diff --git a/modules/libpref/test/unit/test_locked_file_prefs.js b/modules/libpref/test/unit/test_locked_file_prefs.js new file mode 100644 index 0000000000..29b5270df5 --- /dev/null +++ b/modules/libpref/test/unit/test_locked_file_prefs.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +// This file tests the `locked` attribute in default pref files. + +const ps = Services.prefs; + +// It is necessary to manually disable `xpc::IsInAutomation` since +// `resetPrefs` will flip the preference to re-enable `once`-synced +// preference change assertions, and also change the value of those +// preferences. +Services.prefs.setBoolPref( + "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", + false +); + +add_test(function notChangedFromAPI() { + ps.resetPrefs(); + ps.readDefaultPrefsFromFile(do_get_file("data/testPrefLocked.js")); + Assert.strictEqual(ps.getIntPref("testPref.unlocked.int"), 333); + Assert.strictEqual(ps.getIntPref("testPref.locked.int"), 444); + + // Unlocked pref: can set the user value, which is used upon reading. + ps.setIntPref("testPref.unlocked.int", 334); + Assert.ok(ps.prefHasUserValue("testPref.unlocked.int"), "has a user value"); + Assert.strictEqual(ps.getIntPref("testPref.unlocked.int"), 334); + + // Locked pref: can set the user value, but the default value is used upon + // reading. + ps.setIntPref("testPref.locked.int", 445); + Assert.ok(ps.prefHasUserValue("testPref.locked.int"), "has a user value"); + Assert.strictEqual(ps.getIntPref("testPref.locked.int"), 444); + + // After unlocking, the user value is used. + ps.unlockPref("testPref.locked.int"); + Assert.ok(ps.prefHasUserValue("testPref.locked.int"), "has a user value"); + Assert.strictEqual(ps.getIntPref("testPref.locked.int"), 445); + + run_next_test(); +}); + +add_test(function notChangedFromUserPrefs() { + ps.resetPrefs(); + ps.readDefaultPrefsFromFile(do_get_file("data/testPrefLocked.js")); + ps.readUserPrefsFromFile(do_get_file("data/testPrefLockedUser.js")); + + Assert.strictEqual(ps.getIntPref("testPref.unlocked.int"), 333); + Assert.strictEqual(ps.getIntPref("testPref.locked.int"), 444); + + run_next_test(); +}); diff --git a/modules/libpref/test/unit/test_parsePrefs.js b/modules/libpref/test/unit/test_parsePrefs.js new file mode 100644 index 0000000000..53bb55dc7e --- /dev/null +++ b/modules/libpref/test/unit/test_parsePrefs.js @@ -0,0 +1,136 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +"use strict"; + +// // This undoes some of the configuration from +// Services.dirsvc +// .QueryInterface(Ci.nsIDirectoryService) +// .unregisterProvider(provider); + +class PrefObserver { + constructor() { + this.events = []; + } + + onStringPref(...args) { + // // What happens with an exception here? + // throw new Error(`foo ${args[1]}`); + this.events.push(["string", ...args]); + } + + onIntPref(...args) { + this.events.push(["int", ...args]); + } + + onBoolPref(...args) { + this.events.push(["bool", ...args]); + } + + onError(message) { + this.events.push(["error", message]); + } +} + +const TESTS = { + "data/testPrefSticky.js": [ + ["bool", "Default", "testPref.unsticky.bool", true, false, false], + ["bool", "Default", "testPref.sticky.bool", false, true, false], + ], + "data/testPrefStickyUser.js": [ + ["bool", "User", "testPref.sticky.bool", false, false, false], + ], + "data/testPrefLocked.js": [ + ["int", "Default", "testPref.unlocked.int", 333, false, false], + ["int", "Default", "testPref.locked.int", 444, false, true], + ], + // data/testPrefLockedUser is ASCII. + "data/testPrefLockedUser.js": [ + ["int", "User", "testPref.locked.int", 555, false, false], + ], + // data/testPref is ISO-8859. + "data/testPref.js": [ + ["bool", "User", "testPref.bool1", true, false, false], + ["bool", "User", "testPref.bool2", false, false, false], + ["int", "User", "testPref.int1", 23, false, false], + ["int", "User", "testPref.int2", -1236, false, false], + ["string", "User", "testPref.char1", "_testPref", false, false], + ["string", "User", "testPref.char2", "älskar", false, false], + ], + // data/testParsePrefs is data/testPref.js as UTF-8. + "data/testPrefUTF8.js": [ + ["bool", "User", "testPref.bool1", true, false, false], + ["bool", "User", "testPref.bool2", false, false, false], + ["int", "User", "testPref.int1", 23, false, false], + ["int", "User", "testPref.int2", -1236, false, false], + ["string", "User", "testPref.char1", "_testPref", false, false], + // Observe that this is the ISO-8859/Latin-1 encoding of the UTF-8 bytes. + // (Note that this source file is encoded as UTF-8.) This appears to just + // be how libpref handles this case. This test serves as documentation of + // this infelicity. + ["string", "User", "testPref.char2", "älskar", false, false], + ], +}; + +add_task(async function test_success() { + for (let [path, expected] of Object.entries(TESTS)) { + let prefObserver = new PrefObserver(); + + let prefsFile = do_get_file(path); + let data = await IOUtils.read(prefsFile.path); + + Services.prefs.parsePrefsFromBuffer(data, prefObserver, path); + Assert.deepEqual( + prefObserver.events, + expected, + `Observations from ${path} are as expected` + ); + } +}); + +add_task(async function test_exceptions() { + const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" + ); + + let s = `user_pref("testPref.bool1", true); + user_pref("testPref.bool2", false); + user_pref("testPref.int1", 23); + user_pref("testPref.int2", -1236); + `; + + let onErrorCount = 0; + let addPrefCount = 0; + + let marker = "2fc599a1-433b-4de7-bd63-3e69c3bbad5b"; + let addPref = (...args) => { + addPrefCount += 1; + throw new Error(`${marker}${JSON.stringify(args)}${marker}`); + }; + + let { messages } = await AddonTestUtils.promiseConsoleOutput(() => { + Services.prefs.parsePrefsFromBuffer(new TextEncoder().encode(s), { + onStringPref: addPref, + onIntPref: addPref, + onBoolPref: addPref, + onError(message) { + onErrorCount += 1; + console.error(message); + }, + }); + }); + + Assert.equal(addPrefCount, 4); + Assert.equal(onErrorCount, 0); + + // This is fragile but mercifully simple. + Assert.deepEqual( + messages.map(m => JSON.parse(m.message.split(marker)[1])), + [ + ["User", "testPref.bool1", true, false, false], + ["User", "testPref.bool2", false, false, false], + ["User", "testPref.int1", 23, false, false], + ["User", "testPref.int2", -1236, false, false], + ] + ); +}); diff --git a/modules/libpref/test/unit/test_parser.js b/modules/libpref/test/unit/test_parser.js new file mode 100644 index 0000000000..0b37eef47d --- /dev/null +++ b/modules/libpref/test/unit/test_parser.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +// It is necessary to manually disable `xpc::IsInAutomation` since +// `resetPrefs` will flip the preference to re-enable `once`-synced +// preference change assertions, and also change the value of those +// preferences. +Services.prefs.setBoolPref( + "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", + false +); + +function run_test() { + const ps = Services.prefs; + + ps.resetPrefs(); + ps.readDefaultPrefsFromFile(do_get_file("data/testParser.js")); + + Assert.equal(ps.getBoolPref("comment1"), true); + Assert.equal(ps.getBoolPref("comment2"), true); + Assert.equal(ps.getBoolPref("spaced-out"), true); + + Assert.equal(ps.getBoolPref("pref"), true); + Assert.equal(ps.getBoolPref("sticky_pref"), true); + Assert.equal(ps.getBoolPref("user_pref"), true); + Assert.equal(ps.getBoolPref("sticky_pref2"), true); + Assert.equal(ps.getBoolPref("locked_pref"), true); + Assert.equal(ps.getBoolPref("locked_sticky_pref"), true); + Assert.equal(ps.prefIsLocked("locked_pref"), true); + Assert.equal(ps.prefIsLocked("locked_sticky_pref"), true); + + Assert.equal(ps.getBoolPref("bool.true"), true); + Assert.equal(ps.getBoolPref("bool.false"), false); + + Assert.equal(ps.getIntPref("int.0"), 0); + Assert.equal(ps.getIntPref("int.1"), 1); + Assert.equal(ps.getIntPref("int.123"), 123); + Assert.equal(ps.getIntPref("int.+234"), 234); + Assert.equal(ps.getIntPref("int.+ 345"), 345); + Assert.equal(ps.getIntPref("int.-0"), -0); + Assert.equal(ps.getIntPref("int.-1"), -1); + Assert.equal(ps.getIntPref("int.- /* hmm */\t456"), -456); + Assert.equal(ps.getIntPref("int.-\n567"), -567); + Assert.equal(ps.getIntPref("int.INT_MAX-1"), 2147483646); + Assert.equal(ps.getIntPref("int.INT_MAX"), 2147483647); + Assert.equal(ps.getIntPref("int.INT_MIN+2"), -2147483646); + Assert.equal(ps.getIntPref("int.INT_MIN+1"), -2147483647); + Assert.equal(ps.getIntPref("int.INT_MIN"), -2147483648); + + Assert.equal(ps.getCharPref("string.empty"), ""); + Assert.equal(ps.getCharPref("string.abc"), "abc"); + Assert.equal( + ps.getCharPref("string.long"), + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ); + Assert.equal(ps.getCharPref("string.single-quotes"), '"abc"'); + Assert.equal(ps.getCharPref("string.double-quotes"), "'abc'"); + Assert.equal( + ps.getCharPref("string.weird-chars"), + "\x0d \x09 \x0b \x0c \x06 \x16" + ); + Assert.equal(ps.getCharPref("string.escapes"), "\" ' \\ \n \r"); + + // This one is ASCII, so we can use getCharPref() and getStringPref + // interchangeably. + Assert.equal( + ps.getCharPref("string.x-escapes1"), + "Mozilla0\x4d\x6F\x7a\x69\x6c\x6C\x610" + ); + Assert.equal(ps.getStringPref("string.x-escapes1"), "Mozilla0Mozilla0"); + + // This one has chars with value > 127, so it's not valid UTF8, so we can't + // use getStringPref on it. + Assert.equal( + ps.getCharPref("string.x-escapes2"), + "AA A_umlaut\xc4 y_umlaut\xff" + ); + + // The following strings use \uNNNN escapes, which are UTF16 code points. + // libpref stores them internally as UTF8 byte sequences. In each case we get + // the string in two ways: + // - getStringPref() interprets it as UTF8, which is then converted to UTF16 + // in JS. I.e. code points that are multiple bytes in UTF8 become a single + // 16-bit char in JS (except for the non-BMP chars, which become a 16-bit + // surrogate pair). + // - getCharPref() interprets it as Latin1, which is then converted to UTF16 + // in JS. I.e. code points that are multiple bytes in UTF8 become multiple + // 16-bit chars in JS. + + Assert.equal( + ps.getStringPref("string.u-escapes1"), + "A\u0041 A_umlaut\u00c4 y_umlaut\u00ff0" + ); + Assert.equal( + ps.getCharPref("string.u-escapes1"), + "A\x41 A_umlaut\xc3\x84 y_umlaut\xc3\xbf0" + ); + + Assert.equal( + ps.getStringPref("string.u-escapes2"), + "S_acute\u015a y_grave\u1Ef3" + ); + Assert.equal( + ps.getCharPref("string.u-escapes2"), + "S_acute\xc5\x9a y_grave\xe1\xbb\xb3" + ); + + Assert.equal( + ps.getStringPref("string.u-surrogates"), + "cyclone\uD83C\uDF00 grinning_face\uD83D\uDE00" + ); + Assert.equal( + ps.getCharPref("string.u-surrogates"), + "cyclone\xF0\x9F\x8C\x80 grinning_face\xF0\x9F\x98\x80" + ); +} diff --git a/modules/libpref/test/unit/test_stickyprefs.js b/modules/libpref/test/unit/test_stickyprefs.js new file mode 100644 index 0000000000..d344d208ab --- /dev/null +++ b/modules/libpref/test/unit/test_stickyprefs.js @@ -0,0 +1,196 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +const ps = Services.prefs; + +// It is necessary to manually disable `xpc::IsInAutomation` since +// `resetPrefs` will flip the preference to re-enable `once`-synced +// preference change assertions, and also change the value of those +// preferences. +Services.prefs.setBoolPref( + "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", + false +); + +// A little helper to reset the service and load one pref file. +function resetAndLoadDefaults() { + ps.resetPrefs(); + ps.readDefaultPrefsFromFile(do_get_file("data/testPrefSticky.js")); +} + +// A little helper to reset the service and load two pref files. +function resetAndLoadAll() { + ps.resetPrefs(); + ps.readDefaultPrefsFromFile(do_get_file("data/testPrefSticky.js")); + ps.readUserPrefsFromFile(do_get_file("data/testPrefStickyUser.js")); +} + +// A little helper that saves the current state to a file in the profile +// dir, then resets the service and re-reads the file it just saved. +// Used to test what gets actually written - things the pref service decided +// not to write don't exist at all after this call. +function saveAndReload() { + let file = do_get_profile(); + file.append("prefs.js"); + ps.savePrefFile(file); + + // Now reset the pref service and re-read what we saved. + ps.resetPrefs(); + + // Hack alert: on Windows nsLocalFile caches the size of savePrefFile from + // the .create() call above as 0. We call .exists() to reset the cache. + file.exists(); + + ps.readUserPrefsFromFile(file); +} + +// A sticky pref should not be written if the value is unchanged. +add_test(function notWrittenWhenUnchanged() { + resetAndLoadDefaults(); + Assert.strictEqual(ps.getBoolPref("testPref.unsticky.bool"), true); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false); + + // write prefs - but we haven't changed the sticky one, so it shouldn't be written. + saveAndReload(); + // sticky should not have been written to the new file. + try { + ps.getBoolPref("testPref.sticky.bool"); + Assert.ok(false, "expected failure reading this pref"); + } catch (ex) { + Assert.ok(ex, "exception reading regular pref"); + } + run_next_test(); +}); + +// Loading a sticky `pref` then a `user_pref` for the same pref means it should +// always be written. +add_test(function writtenOnceLoadedWithoutChange() { + // Load the same pref file *as well as* a pref file that has a user_pref for + // our sticky with the default value. It should be re-written without us + // touching it. + resetAndLoadAll(); + // reset and re-read what we just wrote - it should be written. + saveAndReload(); + Assert.strictEqual( + ps.getBoolPref("testPref.sticky.bool"), + false, + "user_pref was written with default value" + ); + run_next_test(); +}); + +// If a sticky pref is explicicitly changed, even to the default, it is written. +add_test(function writtenOnceLoadedWithChangeNonDefault() { + // Load the same pref file *as well as* a pref file that has a user_pref for + // our sticky - then change the pref. It should be written. + resetAndLoadAll(); + // Set a new val and check we wrote it. + ps.setBoolPref("testPref.sticky.bool", false); + saveAndReload(); + Assert.strictEqual( + ps.getBoolPref("testPref.sticky.bool"), + false, + "user_pref was written with custom value" + ); + run_next_test(); +}); + +// If a sticky pref is changed to the non-default value, it is written. +add_test(function writtenOnceLoadedWithChangeNonDefault() { + // Load the same pref file *as well as* a pref file that has a user_pref for + // our sticky - then change the pref. It should be written. + resetAndLoadAll(); + // Set a new val and check we wrote it. + ps.setBoolPref("testPref.sticky.bool", true); + saveAndReload(); + Assert.strictEqual( + ps.getBoolPref("testPref.sticky.bool"), + true, + "user_pref was written with custom value" + ); + run_next_test(); +}); + +// Test that prefHasUserValue always returns true whenever there is a sticky +// value, even when that value matches the default. This is mainly for +// about:config semantics - prefs with a sticky value always remain bold and +// always offer "reset" (which fully resets and drops the sticky value as if +// the pref had never changed.) +add_test(function hasUserValue() { + // sticky pref without user value. + resetAndLoadDefaults(); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false); + Assert.ok( + !ps.prefHasUserValue("testPref.sticky.bool"), + "should not initially reflect a user value" + ); + + ps.setBoolPref("testPref.sticky.bool", false); + Assert.ok( + ps.prefHasUserValue("testPref.sticky.bool"), + "should reflect a user value after set to default" + ); + + ps.setBoolPref("testPref.sticky.bool", true); + Assert.ok( + ps.prefHasUserValue("testPref.sticky.bool"), + "should reflect a user value after change to non-default" + ); + + ps.clearUserPref("testPref.sticky.bool"); + Assert.ok( + !ps.prefHasUserValue("testPref.sticky.bool"), + "should reset to no user value" + ); + ps.setBoolPref("testPref.sticky.bool", false, "expected default"); + + // And make sure the pref immediately reflects a user value after load. + resetAndLoadAll(); + Assert.strictEqual(ps.getBoolPref("testPref.sticky.bool"), false); + Assert.ok( + ps.prefHasUserValue("testPref.sticky.bool"), + "should have a user value when loaded value is the default" + ); + run_next_test(); +}); + +// Test that clearUserPref removes the "sticky" value. +add_test(function clearUserPref() { + // load things such that we have a sticky value which is the same as the + // default. + resetAndLoadAll(); + ps.clearUserPref("testPref.sticky.bool"); + + // Once we save prefs the sticky pref should no longer be written. + saveAndReload(); + try { + ps.getBoolPref("testPref.sticky.bool"); + Assert.ok(false, "expected failure reading this pref"); + } catch (ex) { + Assert.ok(ex, "pref doesn't have a sticky value"); + } + run_next_test(); +}); + +// Test that a pref observer gets a notification fired when a sticky pref +// has it's value changed to the same value as the default. The reason for +// this behaviour is that later we might have other code that cares about a +// pref being sticky (IOW, we notify due to the "state" of the pref changing +// even if the value has not) +add_test(function observerFires() { + // load things so there's no sticky value. + resetAndLoadDefaults(); + + function observe(subject, topic, data) { + Assert.equal(data, "testPref.sticky.bool"); + ps.removeObserver("testPref.sticky.bool", observe); + run_next_test(); + } + ps.addObserver("testPref.sticky.bool", observe); + + ps.setBoolPref( + "testPref.sticky.bool", + ps.getBoolPref("testPref.sticky.bool") + ); + // and the observer will fire triggering the next text. +}); diff --git a/modules/libpref/test/unit/test_warnings.js b/modules/libpref/test/unit/test_warnings.js new file mode 100644 index 0000000000..a6782f5c59 --- /dev/null +++ b/modules/libpref/test/unit/test_warnings.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function makeBuffer(length) { + return new Array(length + 1).join("x"); +} + +/** + * @resolves |true| if execution proceeded without warning, + * |false| if there was a warning. + */ +function checkWarning(pref, buffer) { + return new Promise(resolve => { + let complete = false; + let listener = { + observe(event) { + let message = event.message; + if ( + !( + message.startsWith("Warning: attempting to write") && + message.includes(pref) + ) + ) { + return; + } + if (complete) { + return; + } + complete = true; + info("Warning while setting " + pref); + Services.console.unregisterListener(listener); + resolve(true); + }, + }; + do_timeout(1000, function () { + if (complete) { + return; + } + complete = true; + info("No warning while setting " + pref); + Services.console.unregisterListener(listener); + resolve(false); + }); + Services.console.registerListener(listener); + Services.prefs.setCharPref(pref, buffer); + }); +} + +add_task(async function () { + // Simple change, shouldn't cause a warning + info("Checking that a simple change doesn't cause a warning"); + let buf = makeBuffer(100); + let warned = await checkWarning("string.accept", buf); + Assert.ok(!warned); + + // Large change, should cause a warning + info("Checking that a large change causes a warning"); + buf = makeBuffer(32 * 1024); + warned = await checkWarning("string.warn", buf); + Assert.ok(warned); +}); diff --git a/modules/libpref/test/unit/xpcshell.ini b/modules/libpref/test/unit/xpcshell.ini new file mode 100644 index 0000000000..0778b8659f --- /dev/null +++ b/modules/libpref/test/unit/xpcshell.ini @@ -0,0 +1,28 @@ +[DEFAULT] +head = head_libPrefs.js +support-files = + data/testPref.js + extdata/testExt.js + +[test_warnings.js] +[test_bug345529.js] +[test_bug506224.js] +[test_bug577950.js] +[test_bug790374.js] +[test_stickyprefs.js] +skip-if = (os == "win" && socketprocess_networking) +support-files = data/testPrefSticky.js data/testPrefStickyUser.js +[test_locked_file_prefs.js] +skip-if = (os == "win" && socketprocess_networking) +support-files = data/testPrefLocked.js data/testPrefLockedUser.js +[test_changeType.js] +[test_defaultValues.js] +[test_dirtyPrefs.js] +[test_libPrefs.js] +[test_bug1354613.js] +[test_parser.js] +support-files = data/testParser.js +[test_parsePrefs.js] +support-files = + data/testPrefUTF8.js +head = diff --git a/modules/libpref/test/unit_ipc/test_existing_prefs.js b/modules/libpref/test/unit_ipc/test_existing_prefs.js new file mode 100644 index 0000000000..4efce784b0 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_existing_prefs.js @@ -0,0 +1,20 @@ +function isParentProcess() { + let appInfo = Cc["@mozilla.org/xre/app-info;1"]; + return ( + !appInfo || + Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT + ); +} + +function run_test() { + if (!isParentProcess()) { + do_load_child_test_harness(); + + var pb = Services.prefs; + pb.setBoolPref("Test.IPC.bool.new", true); + pb.setIntPref("Test.IPC.int.new", 23); + pb.setCharPref("Test.IPC.char.new", "hey"); + + run_test_in_child("test_observed_prefs.js"); + } +} diff --git a/modules/libpref/test/unit_ipc/test_initial_prefs.js b/modules/libpref/test/unit_ipc/test_initial_prefs.js new file mode 100644 index 0000000000..9c5e8991cf --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_initial_prefs.js @@ -0,0 +1,14 @@ +function isParentProcess() { + return Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function run_test() { + if (!isParentProcess()) { + const pb = Services.prefs; + pb.setBoolPref("Test.IPC.bool", true); + pb.setIntPref("Test.IPC.int", 23); + pb.setCharPref("Test.IPC.char", "hey"); + + run_test_in_child("test_existing_prefs.js"); + } +} diff --git a/modules/libpref/test/unit_ipc/test_large_pref.js b/modules/libpref/test/unit_ipc/test_large_pref.js new file mode 100644 index 0000000000..79c1330c36 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_large_pref.js @@ -0,0 +1,104 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Large preferences should not be set in the child process. +// Non-string preferences are not tested here, because their behavior +// should not be affected by this filtering. +// + +function isParentProcess() { + return Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function makeBuffer(length) { + let string = "x"; + while (string.length < length) { + string = string + string; + } + if (string.length > length) { + string = string.substring(length - string.length); + } + return string; +} + +// from prefapi.h +const MAX_ADVISABLE_PREF_LENGTH = 4 * 1024; + +const largeString = makeBuffer(MAX_ADVISABLE_PREF_LENGTH + 1); +const smallString = makeBuffer(4); + +const testValues = [ + { name: "None", value: undefined }, + { name: "Small", value: smallString }, + { name: "Large", value: largeString }, +]; + +function prefName(def, user) { + return "Test.IPC.default" + def.name + "User" + user.name; +} + +function expectedPrefValue(def, user) { + if (user.value) { + return user.value; + } + return def.value; +} + +function run_test() { + const pb = Services.prefs; + let defaultBranch = pb.getDefaultBranch(""); + + let isParent = isParentProcess(); + if (isParent) { + // Preferences with large values will still appear in the shared memory + // snapshot that we share with all processes. They should not, however, be + // sent with the list of changes on top of the snapshot. + // + // So, make sure we've generated the initial snapshot before we set the + // preference values by launching a child process with an empty test. + sendCommand(""); + + // Set all combinations of none, small and large, for default and user prefs. + for (let def of testValues) { + for (let user of testValues) { + let currPref = prefName(def, user); + if (def.value) { + defaultBranch.setCharPref(currPref, def.value); + } + if (user.value) { + pb.setCharPref(currPref, user.value); + } + } + } + + run_test_in_child("test_large_pref.js"); + } + + // Check that each preference is set or not set, as appropriate. + for (let def of testValues) { + for (let user of testValues) { + if (!def.value && !user.value) { + continue; + } + let pref_name = prefName(def, user); + if (isParent || (def.name != "Large" && user.name != "Large")) { + Assert.equal(pb.getCharPref(pref_name), expectedPrefValue(def, user)); + } else { + // This is the child, and either the default or user value is + // large, so the preference should not be set. + let prefExists; + try { + let val = pb.getCharPref(pref_name); + prefExists = val.length > 128; + } catch (e) { + prefExists = false; + } + ok( + !prefExists, + "Pref " + pref_name + " should not be set in the child" + ); + } + } + } +} diff --git a/modules/libpref/test/unit_ipc/test_locked_prefs.js b/modules/libpref/test/unit_ipc/test_locked_prefs.js new file mode 100644 index 0000000000..037427e495 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_locked_prefs.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Locked status should be communicated to children. + +function isParentProcess() { + return Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function run_test() { + const pb = Services.prefs; + + let bprefname = "Test.IPC.locked.bool"; + let iprefname = "Test.IPC.locked.int"; + let sprefname = "Test.IPC.locked.string"; + + let isParent = isParentProcess(); + if (isParent) { + pb.setBoolPref(bprefname, true); + pb.lockPref(bprefname); + + pb.setIntPref(iprefname, true); + pb.lockPref(iprefname); + + pb.setStringPref(sprefname, true); + pb.lockPref(sprefname); + pb.unlockPref(sprefname); + + run_test_in_child("test_locked_prefs.js"); + } + + ok(pb.prefIsLocked(bprefname), bprefname + " should be locked in the child"); + ok(pb.prefIsLocked(iprefname), iprefname + " should be locked in the child"); + ok( + !pb.prefIsLocked(sprefname), + sprefname + " should be unlocked in the child" + ); +} diff --git a/modules/libpref/test/unit_ipc/test_observed_prefs.js b/modules/libpref/test/unit_ipc/test_observed_prefs.js new file mode 100644 index 0000000000..9f6984d555 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_observed_prefs.js @@ -0,0 +1,12 @@ +function isParentProcess() { + return Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function run_test() { + if (!isParentProcess()) { + const pb = Services.prefs; + Assert.equal(pb.getBoolPref("Test.IPC.bool.new"), true); + Assert.equal(pb.getIntPref("Test.IPC.int.new"), 23); + Assert.equal(pb.getCharPref("Test.IPC.char.new"), "hey"); + } +} diff --git a/modules/libpref/test/unit_ipc/test_sharedMap.js b/modules/libpref/test/unit_ipc/test_sharedMap.js new file mode 100644 index 0000000000..c9a001d7a0 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_sharedMap.js @@ -0,0 +1,362 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// This file tests the functionality of the preference service when using a +// shared memory snapshot. In this configuration, a snapshot of the initial +// state of the preferences database is made when we first spawn a child +// process, and changes after that point are stored as entries in a dynamic hash +// table, on top of the snapshot. + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +let contentPage; + +const { prefs } = Services; +const defaultPrefs = prefs.getDefaultBranch(""); + +const FRAME_SCRIPT_INIT = ` + var { prefs } = Services; + var defaultPrefs = prefs.getDefaultBranch(""); +`; + +function try_(fn) { + try { + return fn(); + } catch (e) { + return undefined; + } +} + +function getPref(pref) { + let flags = { + locked: try_(() => prefs.prefIsLocked(pref)), + hasUser: try_(() => prefs.prefHasUserValue(pref)), + }; + + switch (prefs.getPrefType(pref)) { + case prefs.PREF_INT: + return { + ...flags, + type: "Int", + user: try_(() => prefs.getIntPref(pref)), + default: try_(() => defaultPrefs.getIntPref(pref)), + }; + case prefs.PREF_BOOL: + return { + ...flags, + type: "Bool", + user: try_(() => prefs.getBoolPref(pref)), + default: try_(() => defaultPrefs.getBoolPref(pref)), + }; + case prefs.PREF_STRING: + return { + ...flags, + type: "String", + user: try_(() => prefs.getStringPref(pref)), + default: try_(() => defaultPrefs.getStringPref(pref)), + }; + } + return {}; +} + +function getPrefs(prefNames) { + let result = {}; + for (let pref of prefNames) { + result[pref] = getPref(pref); + } + result.childList = prefs.getChildList(""); + return result; +} + +function checkPref( + pref, + proc, + val, + type, + userVal, + defaultVal, + expectedFlags = {} +) { + info(`Check "${pref}" ${proc} value`); + + equal(val.type, type, `Expected type for "${pref}"`); + equal(val.user, userVal, `Expected user value for "${pref}"`); + + // We only send changes to the content process when they'll make a visible + // difference, so ignore content process default values when we have a defined + // user value. + if (proc !== "content" || val.user === undefined) { + equal(val.default, defaultVal, `Expected default value for "${pref}"`); + } + + for (let [flag, value] of Object.entries(expectedFlags)) { + equal(val[flag], value, `Expected ${flag} value for "${pref}"`); + } +} + +function getPrefList() { + return prefs.getChildList(""); +} + +const TESTS = { + "exists.thenDoesNot": { + beforeContent(PREF) { + prefs.setBoolPref(PREF, true); + + ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); + }, + contentStartup(PREF, val, childList) { + ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); + ok(childList.includes(PREF), `Child list includes "${PREF}"`); + + prefs.clearUserPref(PREF); + ok( + !getPrefList().includes(PREF), + `Parent list doesn't include "${PREF}"` + ); + }, + contentUpdate1(PREF, val, childList) { + ok( + !getPrefList().includes(PREF), + `Parent list doesn't include "${PREF}"` + ); + ok(!childList.includes(PREF), `Child list doesn't include "${PREF}"`); + + prefs.setCharPref(PREF, "foo"); + ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); + checkPref(PREF, "parent", getPref(PREF), "String", "foo"); + }, + contentUpdate2(PREF, val, childList) { + ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); + ok(childList.includes(PREF), `Child list includes "${PREF}"`); + + checkPref(PREF, "parent", getPref(PREF), "String", "foo"); + checkPref(PREF, "child", val, "String", "foo"); + }, + }, + "doesNotExists.thenDoes": { + contentStartup(PREF, val, childList) { + ok( + !getPrefList().includes(PREF), + `Parent list doesn't include "${PREF}"` + ); + ok(!childList.includes(PREF), `Child list doesn't include "${PREF}"`); + + prefs.setIntPref(PREF, 42); + ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); + }, + contentUpdate1(PREF, val, childList) { + ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); + ok(childList.includes(PREF), `Child list includes "${PREF}"`); + + checkPref(PREF, "parent", getPref(PREF), "Int", 42); + checkPref(PREF, "child", val, "Int", 42); + }, + }, +}; + +const PREFS = [ + { type: "Bool", values: [true, false, true] }, + { type: "Int", values: [24, 42, 73] }, + { type: "String", values: ["meh", "hem", "hrm"] }, +]; + +for (let { type, values } of PREFS) { + let set = `set${type}Pref`; + + function prefTest(opts) { + function check( + pref, + proc, + val, + { + expectedVal, + defaultVal = undefined, + expectedDefault = defaultVal, + expectedFlags = {}, + } + ) { + checkPref( + pref, + proc, + val, + type, + expectedVal, + expectedDefault, + expectedFlags + ); + } + + function updatePref( + PREF, + { userVal = undefined, defaultVal = undefined, flags = {} } + ) { + info(`Update "${PREF}"`); + if (userVal !== undefined) { + prefs[set](PREF, userVal); + } + if (defaultVal !== undefined) { + defaultPrefs[set](PREF, defaultVal); + } + if (flags.locked === true) { + prefs.lockPref(PREF); + } else if (flags.locked === false) { + prefs.unlockPref(PREF); + } + } + + return { + beforeContent(PREF) { + updatePref(PREF, opts.initial); + check(PREF, "parent", getPref(PREF), opts.initial); + }, + contentStartup(PREF, contentVal) { + check(PREF, "content", contentVal, opts.initial); + check(PREF, "parent", getPref(PREF), opts.initial); + + updatePref(PREF, opts.change1); + check(PREF, "parent", getPref(PREF), opts.change1); + }, + contentUpdate1(PREF, contentVal) { + check(PREF, "content", contentVal, opts.change1); + check(PREF, "parent", getPref(PREF), opts.change1); + + if (opts.change2) { + updatePref(PREF, opts.change2); + check(PREF, "parent", getPref(PREF), opts.change2); + } + }, + contentUpdate2(PREF, contentVal) { + if (opts.change2) { + check(PREF, "content", contentVal, opts.change2); + check(PREF, "parent", getPref(PREF), opts.change2); + } + }, + }; + } + + for (let i of [0, 1]) { + let userVal = values[i]; + let defaultVal = values[+!i]; + + TESTS[`type.${type}.${i}.default`] = prefTest({ + initial: { defaultVal, expectedVal: defaultVal }, + change1: { defaultVal: values[2], expectedVal: values[2] }, + }); + + TESTS[`type.${type}.${i}.user`] = prefTest({ + initial: { userVal, expectedVal: userVal }, + change1: { defaultVal: values[2], expectedVal: userVal }, + change2: { + userVal: values[2], + expectedDefault: values[2], + expectedVal: values[2], + }, + }); + + TESTS[`type.${type}.${i}.both`] = prefTest({ + initial: { userVal, defaultVal, expectedVal: userVal }, + change1: { defaultVal: values[2], expectedVal: userVal }, + change2: { + userVal: values[2], + expectedDefault: values[2], + expectedVal: values[2], + }, + }); + + TESTS[`type.${type}.${i}.both.thenLock`] = prefTest({ + initial: { userVal, defaultVal, expectedVal: userVal }, + change1: { + expectedDefault: defaultVal, + expectedVal: defaultVal, + flags: { locked: true }, + expectFlags: { locked: true }, + }, + }); + + TESTS[`type.${type}.${i}.both.thenUnlock`] = prefTest({ + initial: { + userVal, + defaultVal, + expectedVal: defaultVal, + flags: { locked: true }, + expectedFlags: { locked: true }, + }, + change1: { + expectedDefault: defaultVal, + expectedVal: userVal, + flags: { locked: false }, + expectFlags: { locked: false }, + }, + }); + + TESTS[`type.${type}.${i}.both.locked`] = prefTest({ + initial: { + userVal, + defaultVal, + expectedVal: defaultVal, + flags: { locked: true }, + expectedFlags: { locked: true }, + }, + change1: { + userVal: values[2], + expectedDefault: defaultVal, + expectedVal: defaultVal, + expectedFlags: { locked: true }, + }, + change2: { + defaultVal: values[2], + expectedDefault: defaultVal, + expectedVal: defaultVal, + expectedFlags: { locked: true }, + }, + }); + } +} + +add_task(async function test_sharedMap_prefs() { + let prefValues = {}; + + async function runChecks(op) { + for (let [pref, ops] of Object.entries(TESTS)) { + if (ops[op]) { + info(`Running ${op} for "${pref}"`); + await ops[op]( + pref, + prefValues[pref] || undefined, + prefValues.childList || undefined + ); + } + } + } + + await runChecks("beforeContent"); + + contentPage = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + registerCleanupFunction(() => contentPage.close()); + + contentPage.addFrameScriptHelper(FRAME_SCRIPT_INIT); + contentPage.addFrameScriptHelper(try_); + contentPage.addFrameScriptHelper(getPref); + + let prefNames = Object.keys(TESTS); + prefValues = await contentPage.legacySpawn(prefNames, getPrefs); + + await runChecks("contentStartup"); + + prefValues = await contentPage.legacySpawn(prefNames, getPrefs); + + await runChecks("contentUpdate1"); + + prefValues = await contentPage.legacySpawn(prefNames, getPrefs); + + await runChecks("contentUpdate2"); +}); diff --git a/modules/libpref/test/unit_ipc/test_sharedMap_static_prefs.js b/modules/libpref/test/unit_ipc/test_sharedMap_static_prefs.js new file mode 100644 index 0000000000..a8181bf2cb --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_sharedMap_static_prefs.js @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// Tests that static preferences in the content process +// correctly handle values which are different from their +// statically-defined defaults. +// +// Since we can't access static preference values from JS, this tests relies on +// assertions in debug builds to detect mismatches. The default and user +// values of two preferences are changed (respectively) before a content +// process is started. Once the content process is launched, the +// preference service asserts that the values stored in all static prefs +// match their current values as known to the preference service. If +// there's a mismatch, the shell will crash, and the test will fail. +// +// For sanity, we also check that the dynamically retrieved preference +// values in the content process match our expectations, though this is +// not strictly part of the test. + +const PREF1_NAME = "dom.webcomponents.shadowdom.report_usage"; +const PREF1_VALUE = false; + +const PREF2_NAME = "dom.mutation-events.cssom.disabled"; +const PREF2_VALUE = true; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const { prefs } = Services; +const defaultPrefs = prefs.getDefaultBranch(""); + +add_task(async function test_sharedMap_static_prefs() { + equal( + prefs.getBoolPref(PREF1_NAME), + PREF1_VALUE, + `Expected initial value for ${PREF1_NAME}` + ); + equal( + prefs.getBoolPref(PREF2_NAME), + PREF2_VALUE, + `Expected initial value for ${PREF2_NAME}` + ); + + defaultPrefs.setBoolPref(PREF1_NAME, !PREF1_VALUE); + prefs.setBoolPref(PREF2_NAME, !PREF2_VALUE); + + equal( + prefs.getBoolPref(PREF1_NAME), + !PREF1_VALUE, + `Expected updated value for ${PREF1_NAME}` + ); + equal( + prefs.getBoolPref(PREF2_NAME), + !PREF2_VALUE, + `Expected updated value for ${PREF2_NAME}` + ); + + let contentPage = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + registerCleanupFunction(() => contentPage.close()); + + /* eslint-disable no-shadow */ + let values = await contentPage.spawn([[PREF1_NAME, PREF2_NAME]], prefs => { + return prefs.map(pref => Services.prefs.getBoolPref(pref)); + }); + /* eslint-enable no-shadow */ + + equal(values[0], !PREF1_VALUE, `Expected content value for ${PREF1_NAME}`); + equal(values[1], !PREF2_VALUE, `Expected content value for ${PREF2_NAME}`); +}); diff --git a/modules/libpref/test/unit_ipc/test_update_prefs.js b/modules/libpref/test/unit_ipc/test_update_prefs.js new file mode 100644 index 0000000000..51e2c458c5 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_update_prefs.js @@ -0,0 +1,34 @@ +function isParentProcess() { + return Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +} + +function run_test() { + if (isParentProcess()) { + do_load_child_test_harness(); + + var pb = Services.prefs; + + // these prefs are set after the child has been created. + pb.setBoolPref("Test.IPC.bool.new", true); + pb.setIntPref("Test.IPC.int.new", 23); + pb.setCharPref("Test.IPC.char.new", "hey"); + + run_test_in_child("test_observed_prefs.js", testPrefClear); + } +} + +function testPrefClear() { + var pb = Services.prefs; + pb.clearUserPref("Test.IPC.bool.new"); + + sendCommand( + 'var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);\n' + + 'pb.prefHasUserValue("Test.IPC.bool.new");\n', + checkWasCleared + ); +} + +function checkWasCleared(existsStr) { + Assert.equal(existsStr, "false"); + do_test_finished(); +} diff --git a/modules/libpref/test/unit_ipc/test_user_default_prefs.js b/modules/libpref/test/unit_ipc/test_user_default_prefs.js new file mode 100644 index 0000000000..3307522513 --- /dev/null +++ b/modules/libpref/test/unit_ipc/test_user_default_prefs.js @@ -0,0 +1,72 @@ +const pb = Services.prefs; + +// This pref is chosen somewhat arbitrarily --- we just need one +// that's guaranteed to have a default value. +const kPrefName = "intl.accept_languages"; // of type char, which we +// assume below +var initialValue = null; + +function check_child_pref_info_eq(continuation) { + sendCommand( + 'var pb = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);\n' + + // Returns concatenation "[value],[isUser]" + 'pb.getCharPref("' + + kPrefName + + '")+ "," +' + + 'pb.prefHasUserValue("' + + kPrefName + + '");', + function (info) { + let [value, isUser] = info.split(","); + Assert.equal(pb.getCharPref(kPrefName), value); + Assert.equal(pb.prefHasUserValue(kPrefName), isUser == "true"); + continuation(); + } + ); +} + +function run_test() { + // We finish in clean_up() + do_test_pending(); + + initialValue = pb.getCharPref(kPrefName); + + test_user_setting(); +} + +function test_user_setting() { + // We rely on setting this before the content process starts up. + // When it starts up, it should recognize this as a user pref, not + // a default pref. + pb.setCharPref(kPrefName, "i-imaginarylanguage"); + // NB: processing of the value-change notification in the child + // process triggered by the above set happens-before the remaining + // code here + check_child_pref_info_eq(function () { + Assert.equal(pb.prefHasUserValue(kPrefName), true); + + test_cleared_is_default(); + }); +} + +function test_cleared_is_default() { + pb.clearUserPref(kPrefName); + // NB: processing of the value-change notification in the child + // process triggered by the above set happens-before the remaining + // code here + check_child_pref_info_eq(function () { + Assert.equal(pb.prefHasUserValue(kPrefName), false); + + clean_up(); + }); +} + +function clean_up() { + pb.setCharPref(kPrefName, initialValue); + // NB: processing of the value-change notification in the child + // process triggered by the above set happens-before the remaining + // code here + check_child_pref_info_eq(function () { + do_test_finished(); + }); +} diff --git a/modules/libpref/test/unit_ipc/xpcshell.ini b/modules/libpref/test/unit_ipc/xpcshell.ini new file mode 100644 index 0000000000..e2a444f774 --- /dev/null +++ b/modules/libpref/test/unit_ipc/xpcshell.ini @@ -0,0 +1,14 @@ +[DEFAULT] +head = +skip-if = toolkit == 'android' + +[test_existing_prefs.js] +[test_initial_prefs.js] +[test_large_pref.js] +[test_locked_prefs.js] +[test_observed_prefs.js] +[test_update_prefs.js] +[test_sharedMap.js] +[test_sharedMap_static_prefs.js] +skip-if = !debug # Relies on debug assertions to catch failure cases. +[test_user_default_prefs.js] |