summaryrefslogtreecommitdiffstats
path: root/modules/libpref/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /modules/libpref/test
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--modules/libpref/test/browser/browser.ini9
-rw-r--r--modules/libpref/test/browser/browser_sanitization_events.js89
-rw-r--r--modules/libpref/test/browser/file_access_sanitized_pref.html10
-rw-r--r--modules/libpref/test/gtest/Basics.cpp58
-rw-r--r--modules/libpref/test/gtest/Parser.cpp496
-rw-r--r--modules/libpref/test/gtest/moz.build23
-rw-r--r--modules/libpref/test/python.ini4
-rw-r--r--modules/libpref/test/test_generate_static_pref_list.py493
-rw-r--r--modules/libpref/test/unit/data/testParser.js98
-rw-r--r--modules/libpref/test/unit/data/testPref.js6
-rw-r--r--modules/libpref/test/unit/data/testPrefLocked.js2
-rw-r--r--modules/libpref/test/unit/data/testPrefLockedUser.js3
-rw-r--r--modules/libpref/test/unit/data/testPrefSticky.js2
-rw-r--r--modules/libpref/test/unit/data/testPrefStickyUser.js5
-rw-r--r--modules/libpref/test/unit/data/testPrefUTF8.js6
-rw-r--r--modules/libpref/test/unit/extdata/testExt.js2
-rw-r--r--modules/libpref/test/unit/head_libPrefs.js37
-rw-r--r--modules/libpref/test/unit/test_bug1354613.js21
-rw-r--r--modules/libpref/test/unit/test_bug345529.js25
-rw-r--r--modules/libpref/test/unit/test_bug506224.js25
-rw-r--r--modules/libpref/test/unit/test_bug577950.js17
-rw-r--r--modules/libpref/test/unit/test_bug790374.js50
-rw-r--r--modules/libpref/test/unit/test_changeType.js164
-rw-r--r--modules/libpref/test/unit/test_defaultValues.js59
-rw-r--r--modules/libpref/test/unit/test_dirtyPrefs.js69
-rw-r--r--modules/libpref/test/unit/test_libPrefs.js450
-rw-r--r--modules/libpref/test/unit/test_locked_file_prefs.js51
-rw-r--r--modules/libpref/test/unit/test_parsePrefs.js136
-rw-r--r--modules/libpref/test/unit/test_parser.js116
-rw-r--r--modules/libpref/test/unit/test_stickyprefs.js196
-rw-r--r--modules/libpref/test/unit/test_warnings.js62
-rw-r--r--modules/libpref/test/unit/xpcshell.ini28
-rw-r--r--modules/libpref/test/unit_ipc/test_existing_prefs.js20
-rw-r--r--modules/libpref/test/unit_ipc/test_initial_prefs.js14
-rw-r--r--modules/libpref/test/unit_ipc/test_large_pref.js104
-rw-r--r--modules/libpref/test/unit_ipc/test_locked_prefs.js39
-rw-r--r--modules/libpref/test/unit_ipc/test_observed_prefs.js12
-rw-r--r--modules/libpref/test/unit_ipc/test_sharedMap.js362
-rw-r--r--modules/libpref/test/unit_ipc/test_sharedMap_static_prefs.js76
-rw-r--r--modules/libpref/test/unit_ipc/test_update_prefs.js34
-rw-r--r--modules/libpref/test/unit_ipc/test_user_default_prefs.js72
-rw-r--r--modules/libpref/test/unit_ipc/xpcshell.ini14
42 files changed, 3559 insertions, 0 deletions
diff --git a/modules/libpref/test/browser/browser.ini b/modules/libpref/test/browser/browser.ini
new file mode 100644
index 0000000000..f657000718
--- /dev/null
+++ b/modules/libpref/test/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+
+support-files =
+ file_access_sanitized_pref.html
+
+[browser_sanitization_events.js]
+skip-if = ccov
+ tsan
+ (os == 'linux' && asan)
diff --git a/modules/libpref/test/browser/browser_sanitization_events.js b/modules/libpref/test/browser/browser_sanitization_events.js
new file mode 100644
index 0000000000..f71bd04879
--- /dev/null
+++ b/modules/libpref/test/browser/browser_sanitization_events.js
@@ -0,0 +1,89 @@
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+// eslint-disable-next-line
+const ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+const PAGE_URL = ROOT + "file_access_sanitized_pref.html";
+
+async function waitForEventCount(
+ count,
+ process = "content",
+ category = "security",
+ method = "prefUsage"
+) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 100));
+ let returned_events = await TestUtils.waitForCondition(() => {
+ let events = Services.telemetry.snapshotEvents(
+ Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
+ false
+ )[process];
+
+ if (!events) {
+ return null;
+ }
+
+ events = events.filter(e => e[1] == category && e[2] == method);
+ dump(`Waiting for ${count} events, got ${events.length}\n`);
+ return events.length >= count ? events : null;
+ }, "waiting for telemetry event count of: " + count);
+ return returned_events;
+}
+
+add_task(async function sanitized_pref_test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["fission.omitBlocklistedPrefsInSubprocesses", true],
+ ["fission.enforceBlocklistedPrefsInSubprocesses", false],
+ ],
+ });
+
+ Services.telemetry.setEventRecordingEnabled("security", true);
+ Services.telemetry.clearEvents();
+
+ TelemetryTestUtils.assertNumberOfEvents(0, { process: "content" });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: PAGE_URL },
+ async function (browser) {}
+ );
+
+ // Needed because otherwise we advance too quickly
+ await waitForEventCount(1);
+
+ let events = TelemetryTestUtils.getEvents(
+ { category: "security", method: "prefUsage", object: "contentProcess" },
+ { process: "content" }
+ );
+
+ let count = 0,
+ foundIt = false;
+ for (let i = 0; i < events.length; i++) {
+ if (events[i].value == "extensions.webextensions.uuids") {
+ foundIt = true;
+ count++;
+ }
+ }
+
+ // We may have more than one event entries because we take two paths in the preference code
+ // in this access pattern, but in other patterns we may only take one of those
+ // paths, so we happen to count it twice this way. No big deal. Sometimes we even have 4
+ // or 6 based on timing
+ dump(
+ `We found ${events.length} events, ${count} of which were 'extensions.webextensions.uuids'.`
+ );
+
+ // eslint-disable-next-line
+ Assert.ok(
+ foundIt,
+ "We did not find an event for 'extensions.webextensions.uuids'"
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/modules/libpref/test/browser/file_access_sanitized_pref.html b/modules/libpref/test/browser/file_access_sanitized_pref.html
new file mode 100644
index 0000000000..ae21abb2ad
--- /dev/null
+++ b/modules/libpref/test/browser/file_access_sanitized_pref.html
@@ -0,0 +1,10 @@
+
+
+<!DOCTYPE HTML>
+<html>
+<body>
+ <script>
+ SpecialPowers.getCharPref("extensions.webextensions.uuids", "<na>");
+ </script>
+</body>
+</html>
diff --git a/modules/libpref/test/gtest/Basics.cpp b/modules/libpref/test/gtest/Basics.cpp
new file mode 100644
index 0000000000..1bf8561a0d
--- /dev/null
+++ b/modules/libpref/test/gtest/Basics.cpp
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+
+TEST(PrefsBasics, Errors)
+{
+ Preferences::SetBool("foo.bool", true, PrefValueKind::Default);
+ Preferences::SetBool("foo.bool", false, PrefValueKind::User);
+ ASSERT_EQ(Preferences::GetBool("foo.bool", false, PrefValueKind::Default),
+ true);
+ ASSERT_EQ(Preferences::GetBool("foo.bool", true, PrefValueKind::User), false);
+
+ Preferences::SetInt("foo.int", -66, PrefValueKind::Default);
+ Preferences::SetInt("foo.int", -77, PrefValueKind::User);
+ ASSERT_EQ(Preferences::GetInt("foo.int", 1, PrefValueKind::Default), -66);
+ ASSERT_EQ(Preferences::GetInt("foo.int", 1, PrefValueKind::User), -77);
+
+ Preferences::SetUint("foo.uint", 88, PrefValueKind::Default);
+ Preferences::SetUint("foo.uint", 99, PrefValueKind::User);
+ ASSERT_EQ(Preferences::GetUint("foo.uint", 1, PrefValueKind::Default), 88U);
+ ASSERT_EQ(Preferences::GetUint("foo.uint", 1, PrefValueKind::User), 99U);
+
+ Preferences::SetFloat("foo.float", 3.33f, PrefValueKind::Default);
+ Preferences::SetFloat("foo.float", 4.44f, PrefValueKind::User);
+ ASSERT_FLOAT_EQ(
+ Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::Default), 3.33f);
+ ASSERT_FLOAT_EQ(Preferences::GetFloat("foo.float", 1.0f, PrefValueKind::User),
+ 4.44f);
+}
+
+TEST(PrefsBasics, Serialize)
+{
+ // Ensure that at least this one preference exists
+ Preferences::SetBool("foo.bool", true, PrefValueKind::Default);
+ ASSERT_EQ(Preferences::GetBool("foo.bool", false, PrefValueKind::Default),
+ true);
+
+ nsCString str;
+ Preferences::SerializePreferences(str, true);
+ fprintf(stderr, "%s\n", str.Data());
+ // Assert that some prefs were not sanitized
+ ASSERT_NE(nullptr, strstr(str.Data(), "B--:"));
+ ASSERT_NE(nullptr, strstr(str.Data(), "I--:"));
+ ASSERT_NE(nullptr, strstr(str.Data(), "S--:"));
+ // Assert that something was sanitized
+ ASSERT_NE(
+ nullptr,
+ strstr(
+ str.Data(),
+ "I-S:56/datareporting.policy.dataSubmissionPolicyAcceptedVersion"));
+}
diff --git a/modules/libpref/test/gtest/Parser.cpp b/modules/libpref/test/gtest/Parser.cpp
new file mode 100644
index 0000000000..972d32cfca
--- /dev/null
+++ b/modules/libpref/test/gtest/Parser.cpp
@@ -0,0 +1,496 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
+#include "Preferences.h"
+
+using namespace mozilla;
+
+// Keep this in sync with the declaration in Preferences.cpp.
+//
+// It's declared here to avoid polluting Preferences.h with test-only stuff.
+void TestParseError(PrefValueKind aKind, const char* aText,
+ nsCString& aErrorMsg);
+
+TEST(PrefsParser, Errors)
+{
+ nsAutoCStringN<128> actualErrorMsg;
+
+// Use a macro rather than a function so that the line number reported by
+// gtest on failure is useful.
+#define P(kind_, text_, expectedErrorMsg_) \
+ do { \
+ TestParseError(kind_, text_, actualErrorMsg); \
+ ASSERT_STREQ(expectedErrorMsg_, actualErrorMsg.get()); \
+ } while (0)
+
+#define DEFAULT(text_, expectedErrorMsg_) \
+ P(PrefValueKind::Default, text_, expectedErrorMsg_)
+
+#define USER(text_, expectedErrorMsg_) \
+ P(PrefValueKind::User, text_, expectedErrorMsg_)
+
+ // clang-format off
+
+ //-------------------------------------------------------------------------
+ // Valid syntax. (Other testing of more typical valid syntax and semantics is
+ // done in modules/libpref/test/unit/test_parser.js.)
+ //-------------------------------------------------------------------------
+
+ // Normal prefs.
+ DEFAULT(R"(
+pref("bool", true);
+sticky_pref("int", 123);
+user_pref("string", "value");
+ )",
+ ""
+ );
+
+ // Totally empty input.
+ DEFAULT("", "");
+
+ // Whitespace-only input.
+ DEFAULT(R"(
+
+ )" "\v \t \v \f",
+ ""
+ );
+
+ // Comment-only inputs.
+ DEFAULT(R"(// blah)", "");
+ DEFAULT(R"(# blah)", "");
+ DEFAULT(R"(/* blah */)", "");
+
+ //-------------------------------------------------------------------------
+ // All the lexing errors. (To be pedantic, some of the integer literal
+ // overflows are triggered in the parser, but put them all here so they're all
+ // in the one spot.)
+ //-------------------------------------------------------------------------
+
+ // Integer overflow errors.
+ DEFAULT(R"(
+pref("int.ok", 2147483647);
+pref("int.overflow", 2147483648);
+pref("int.ok", +2147483647);
+pref("int.overflow", +2147483648);
+pref("int.ok", -2147483648);
+pref("int.overflow", -2147483649);
+pref("int.overflow", 4294967296);
+pref("int.overflow", +4294967296);
+pref("int.overflow", -4294967296);
+pref("int.overflow", 4294967297);
+pref("int.overflow", 1234567890987654321);
+ )",
+ "test:3: prefs parse error: integer literal overflowed\n"
+ "test:5: prefs parse error: integer literal overflowed\n"
+ "test:7: prefs parse error: integer literal overflowed\n"
+ "test:8: prefs parse error: integer literal overflowed\n"
+ "test:9: prefs parse error: integer literal overflowed\n"
+ "test:10: prefs parse error: integer literal overflowed\n"
+ "test:11: prefs parse error: integer literal overflowed\n"
+ "test:12: prefs parse error: integer literal overflowed\n"
+ );
+
+ // Other integer errors.
+ DEFAULT(R"(
+pref("int.unexpected", 100foo);
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: unexpected character in integer literal\n"
+ );
+
+ // \x00 is not allowed.
+ DEFAULT(R"(
+pref("string.bad-x-escape", "foo\x00bar");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: \\x00 is not allowed\n"
+ );
+
+ // Various bad things after \x: end of string, punctuation, space, newline,
+ // EOF.
+ DEFAULT(R"(
+pref("string.bad-x-escape", "foo\x");
+pref("string.bad-x-escape", "foo\x,bar");
+pref("string.bad-x-escape", "foo\x 12");
+pref("string.bad-x-escape", "foo\x
+12");
+pref("string.bad-x-escape", "foo\x)",
+ "test:2: prefs parse error: malformed \\x escape sequence\n"
+ "test:3: prefs parse error: malformed \\x escape sequence\n"
+ "test:4: prefs parse error: malformed \\x escape sequence\n"
+ "test:5: prefs parse error: malformed \\x escape sequence\n"
+ "test:7: prefs parse error: malformed \\x escape sequence\n"
+ );
+
+ // Not enough hex digits.
+ DEFAULT(R"(
+pref("string.bad-x-escape", "foo\x1");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: malformed \\x escape sequence\n"
+ );
+
+ // Invalid hex digit.
+ DEFAULT(R"(
+pref("string.bad-x-escape", "foo\x1G");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: malformed \\x escape sequence\n"
+ );
+
+ // \u0000 is not allowed.
+ // (The string literal is broken in two so that MSVC doesn't complain about
+ // an invalid universal-character-name.)
+ DEFAULT(R"(
+pref("string.bad-u-escape", "foo\)" R"(u0000 bar");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: \\u0000 is not allowed\n"
+ );
+
+ // Various bad things after \u: end of string, punctuation, space, newline,
+ // EOF.
+ DEFAULT(R"(
+pref("string.bad-u-escape", "foo\u");
+pref("string.bad-u-escape", "foo\u,bar");
+pref("string.bad-u-escape", "foo\u 1234");
+pref("string.bad-u-escape", "foo\u
+1234");
+pref("string.bad-u-escape", "foo\u)",
+ "test:2: prefs parse error: malformed \\u escape sequence\n"
+ "test:3: prefs parse error: malformed \\u escape sequence\n"
+ "test:4: prefs parse error: malformed \\u escape sequence\n"
+ "test:5: prefs parse error: malformed \\u escape sequence\n"
+ "test:7: prefs parse error: malformed \\u escape sequence\n"
+ );
+
+ // Not enough hex digits.
+ DEFAULT(R"(
+pref("string.bad-u-escape", "foo\u1");
+pref("string.bad-u-escape", "foo\u12");
+pref("string.bad-u-escape", "foo\u123");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: malformed \\u escape sequence\n"
+ "test:3: prefs parse error: malformed \\u escape sequence\n"
+ "test:4: prefs parse error: malformed \\u escape sequence\n"
+ );
+
+ // Invalid hex digit.
+ DEFAULT(R"(
+pref("string.bad-u-escape", "foo\u1G34");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: malformed \\u escape sequence\n"
+ );
+
+ // High surrogate not followed by low surrogate.
+ // (The string literal is broken in two so that MSVC doesn't complain about
+ // an invalid universal-character-name.)
+ DEFAULT(R"(
+pref("string.bad-u-surrogate", "foo\)" R"(ud83c,blah");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: expected low surrogate after high surrogate\n"
+ );
+
+ // High surrogate followed by invalid low surrogate.
+ // (The string literal is broken in two so that MSVC doesn't complain about
+ // an invalid universal-character-name.)
+ DEFAULT(R"(
+pref("string.bad-u-surrogate", "foo\)" R"(ud83c\u1234");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: invalid low surrogate after high surrogate\n"
+ );
+
+ // Low surrogate not preceded by high surrogate.
+ // (The string literal is broken in two so that MSVC doesn't complain about
+ // an invalid universal-character-name.)
+ DEFAULT(R"(
+pref("string.bad-u-surrogate", "foo\)" R"(udc00");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: expected high surrogate before low surrogate\n"
+ );
+
+ // Unlike in JavaScript, \b, \f, \t, \v aren't allowed.
+ DEFAULT(R"(
+pref("string.bad-escape", "foo\b");
+pref("string.bad-escape", "foo\f");
+pref("string.bad-escape", "foo\t");
+pref("string.bad-escape", "foo\v");
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:3: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:4: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:5: prefs parse error: unexpected escape sequence character after '\\'\n"
+ );
+
+ // Various bad things after \: non-special letter, number, punctuation,
+ // space, newline, EOF.
+ DEFAULT(R"(
+pref("string.bad-escape", "foo\Q");
+pref("string.bad-escape", "foo\1");
+pref("string.bad-escape", "foo\,");
+pref("string.bad-escape", "foo\ n");
+pref("string.bad-escape", "foo\
+n");
+pref("string.bad-escape", "foo\)",
+ "test:2: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:3: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:4: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:5: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:6: prefs parse error: unexpected escape sequence character after '\\'\n"
+ "test:8: prefs parse error: unexpected escape sequence character after '\\'\n"
+ );
+
+ // Unterminated string literals.
+
+ // Simple case.
+ DEFAULT(R"(
+pref("string.unterminated-string", "foo
+ )",
+ "test:3: prefs parse error: unterminated string literal\n"
+ );
+
+ // Alternative case; `int` comes after the string and is seen as a keyword.
+ // The parser then skips to the ';', so no error about the unterminated
+ // string is issued.
+ DEFAULT(R"(
+pref("string.unterminated-string", "foo);
+pref("int.ok", 0);
+ )",
+ "test:3: prefs parse error: unknown keyword\n"
+ );
+
+ // Mismatched quotes (1).
+ DEFAULT(R"(
+pref("string.unterminated-string", "foo');
+ )",
+ "test:3: prefs parse error: unterminated string literal\n"
+ );
+
+ // Mismatched quotes (2).
+ DEFAULT(R"(
+pref("string.unterminated-string", 'foo");
+ )",
+ "test:3: prefs parse error: unterminated string literal\n"
+ );
+
+ // Unknown keywords.
+ DEFAULT(R"(
+foo;
+preff("string.bad-keyword", true);
+ticky_pref("string.bad-keyword", true);
+User_pref("string.bad-keyword", true);
+pref("string.bad-keyword", TRUE);
+ )",
+ "test:2: prefs parse error: unknown keyword\n"
+ "test:3: prefs parse error: unknown keyword\n"
+ "test:4: prefs parse error: unknown keyword\n"
+ "test:5: prefs parse error: unknown keyword\n"
+ "test:6: prefs parse error: unknown keyword\n"
+ );
+
+ // Unterminated C-style comment.
+ DEFAULT(R"(
+/* comment
+ )",
+ "test:3: prefs parse error: unterminated /* comment\n"
+ );
+
+ // Malformed comments (single slashes), followed by whitespace, newline, EOF.
+ DEFAULT(R"(
+/ comment;
+/
+; /)",
+ "test:2: prefs parse error: expected '/' or '*' after '/'\n"
+ "test:3: prefs parse error: expected '/' or '*' after '/'\n"
+ "test:4: prefs parse error: expected '/' or '*' after '/'\n"
+ );
+
+ // C++-style comment ending in EOF (1).
+ DEFAULT(R"(
+// comment)",
+ ""
+ );
+
+ // C++-style comment ending in EOF (2).
+ DEFAULT(R"(
+//)",
+ ""
+ );
+
+ // Various unexpected characters.
+ DEFAULT(R"(
+pref("unexpected.chars", &true);
+pref("unexpected.chars" : true);
+@pref("unexpected.chars", true);
+pref["unexpected.chars": true];
+ )",
+ "test:2: prefs parse error: unexpected character\n"
+ "test:3: prefs parse error: unexpected character\n"
+ "test:4: prefs parse error: unexpected character\n"
+ "test:5: prefs parse error: unexpected character\n"
+ );
+
+ //-------------------------------------------------------------------------
+ // All the parsing errors.
+ //-------------------------------------------------------------------------
+
+ DEFAULT(R"(
+"pref"("parse.error": true);
+pref1("parse.error": true);
+pref(123: true);
+pref("parse.error" true);
+pref("parse.error", pref);
+pref("parse.error", -true);
+pref("parse.error", +"value");
+pref("parse.error", true,);
+pref("parse.error", true;
+pref("parse.error", true, sticky, locked;
+pref("parse.error", true)
+pref("int.ok", 1);
+pref("parse.error", true))",
+ "test:2: prefs parse error: expected pref specifier at start of pref definition\n"
+ "test:3: prefs parse error: expected '(' after pref specifier\n"
+ "test:4: prefs parse error: expected pref name after '('\n"
+ "test:5: prefs parse error: expected ',' after pref name\n"
+ "test:6: prefs parse error: expected pref value after ','\n"
+ "test:7: prefs parse error: expected integer literal after '-'\n"
+ "test:8: prefs parse error: expected integer literal after '+'\n"
+ "test:9: prefs parse error: expected pref attribute after ','\n"
+ "test:10: prefs parse error: expected ',' or ')' after pref value\n"
+ "test:11: prefs parse error: expected ',' or ')' after pref attribute\n"
+ "test:13: prefs parse error: expected ';' after ')'\n"
+ "test:14: prefs parse error: expected ';' after ')'\n"
+ );
+
+ USER(R"(
+pref("parse.error", true);
+sticky_pref("parse.error", true);
+user_pref("int.ok", 1);
+ )",
+ "test:2: prefs parse error: expected 'user_pref' at start of pref definition\n"
+ "test:3: prefs parse error: expected 'user_pref' at start of pref definition\n"
+ );
+
+ USER(R"(
+user_pref("parse.error", true;
+user_pref("int.ok", 1);
+ )",
+ "test:2: prefs parse error: expected ')' after pref value\n"
+ );
+
+ // Parse errors involving unexpected EOF.
+
+ DEFAULT(R"(
+pref)",
+ "test:2: prefs parse error: expected '(' after pref specifier\n"
+ );
+
+ DEFAULT(R"(
+pref()",
+ "test:2: prefs parse error: expected pref name after '('\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error")",
+ "test:2: prefs parse error: expected ',' after pref name\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error",)",
+ "test:2: prefs parse error: expected pref value after ','\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error", -)",
+ "test:2: prefs parse error: expected integer literal after '-'\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error", +)",
+ "test:2: prefs parse error: expected integer literal after '+'\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error", true)",
+ "test:2: prefs parse error: expected ',' or ')' after pref value\n"
+ );
+
+ USER(R"(
+user_pref("parse.error", true)",
+ "test:2: prefs parse error: expected ')' after pref value\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error", true,)",
+ "test:2: prefs parse error: expected pref attribute after ','\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error", true, sticky)",
+ "test:2: prefs parse error: expected ',' or ')' after pref attribute\n"
+ );
+
+ DEFAULT(R"(
+pref("parse.error", true))",
+ "test:2: prefs parse error: expected ';' after ')'\n"
+ );
+
+ // This is something we saw in practice with the old parser, which allowed
+ // repeated semicolons.
+ DEFAULT(R"(
+pref("parse.error", true);;
+pref("parse.error", true, locked);;;
+pref("parse.error", true, sticky, locked);;;;
+pref("int.ok", 0);
+ )",
+ "test:2: prefs parse error: expected pref specifier at start of pref definition\n"
+ "test:3: prefs parse error: expected pref specifier at start of pref definition\n"
+ "test:3: prefs parse error: expected pref specifier at start of pref definition\n"
+ "test:4: prefs parse error: expected pref specifier at start of pref definition\n"
+ "test:4: prefs parse error: expected pref specifier at start of pref definition\n"
+ "test:4: prefs parse error: expected pref specifier at start of pref definition\n"
+ );
+
+ //-------------------------------------------------------------------------
+ // Invalid syntax after various newline combinations, for the purpose of
+ // testing that line numbers are correct.
+ //-------------------------------------------------------------------------
+
+ // In all of the following we have a \n, a \r, a \r\n, and then an error, so
+ // the error is on line 4. (Note: these ones don't use raw string literals
+ // because MSVC somehow swallows any \r that appears in them.)
+
+ DEFAULT("\n \r \r\n bad",
+ "test:4: prefs parse error: unknown keyword\n"
+ );
+
+ DEFAULT("#\n#\r#\r\n bad",
+ "test:4: prefs parse error: unknown keyword\n"
+ );
+
+ DEFAULT("//\n//\r//\r\n bad",
+ "test:4: prefs parse error: unknown keyword\n"
+ );
+
+ DEFAULT("/*\n \r \r\n*/ bad",
+ "test:4: prefs parse error: unknown keyword\n"
+ );
+
+ // Note: the escape sequences do *not* affect the line number.
+ DEFAULT("pref(\"foo\\n\n foo\\r\r foo\\r\\n\r\n foo\", bad);",
+ "test:4: prefs parse error: unknown keyword\n"
+ );
+
+ // clang-format on
+}
diff --git a/modules/libpref/test/gtest/moz.build b/modules/libpref/test/gtest/moz.build
new file mode 100644
index 0000000000..d689b382f8
--- /dev/null
+++ b/modules/libpref/test/gtest/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library("libpreftests")
+
+LOCAL_INCLUDES += [
+ "../..",
+]
+
+UNIFIED_SOURCES = [
+ "Basics.cpp",
+ "Parser.cpp",
+]
+
+# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
+# to work around, so we just ignore it.
+if CONFIG["CC_TYPE"] == "clang":
+ CXXFLAGS += ["-Wno-inconsistent-missing-override"]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/modules/libpref/test/python.ini b/modules/libpref/test/python.ini
new file mode 100644
index 0000000000..dd54269c68
--- /dev/null
+++ b/modules/libpref/test/python.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+subsuite = mozbuild
+
+[test_generate_static_pref_list.py]
diff --git a/modules/libpref/test/test_generate_static_pref_list.py b/modules/libpref/test/test_generate_static_pref_list.py
new file mode 100644
index 0000000000..2c8797099e
--- /dev/null
+++ b/modules/libpref/test/test_generate_static_pref_list.py
@@ -0,0 +1,493 @@
+# 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/.
+
+import sys
+import unittest
+from os import path
+
+import mozpack.path as mozpath
+import mozunit
+import yaml
+
+try:
+ from StringIO import StringIO
+except ImportError:
+ from io import StringIO
+
+sys.path.append(path.join(path.dirname(__file__), ".."))
+from init.generate_static_pref_list import generate_code
+
+test_data_path = mozpath.abspath(mozpath.dirname(__file__))
+test_data_path = mozpath.join(test_data_path, "data")
+
+# A single good input with lots of different combinations.
+good_input = """
+- name: my.bool
+ type: bool
+ value: false
+ mirror: never
+
+- name: my.int
+ type: int32_t
+ value: -123
+ mirror: once
+ do_not_use_directly: false
+ rust: false
+
+- mirror: always
+ value: 999
+ type: uint32_t
+ name: my.uint
+ rust: true
+
+- name: my.float # A comment.
+ type: float # A comment.
+ do_not_use_directly: true # A comment.
+ value: 0.0f # A comment.
+ mirror: once # A comment.
+ rust: true # A comment.
+
+# A comment.
+- name: my.string
+ type: String
+ value: foo"bar # The double quote needs escaping.
+ mirror: never
+ include: foobar.h
+
+# A comment.
+- name: my.string2
+ type: String
+ value: "foobar" # This string is quoted.
+ mirror: never
+
+# A comment.
+- name: my.atomic.bool
+ type: RelaxedAtomicBool
+ value: true
+ mirror: always
+ rust: true
+
+# A comment.
+- name: my.datamutex.string
+ type: DataMutexString
+ value: "foobar" # This string is quoted.
+ mirror: always
+
+# Mirrored string-valued prefs are interesting in Rust.
+- name: my.datamutex.string.rust
+ type: DataMutexString
+ value: ""
+ mirror: always
+ rust: true
+
+# YAML+Python interprets `10 + 10 * 20` as a string, and so it is printed
+# unchanged.
+- name: my.atomic.int
+ type: ReleaseAcquireAtomicInt32
+ value: 10 + 10 * 20
+ mirror: always
+ do_not_use_directly: true # A comment.
+
+# YAML+Python changes `0x44` to `68` because it interprets the value as an
+# integer.
+- name: my.atomic.uint
+ type: SequentiallyConsistentAtomicUint32
+ value: 0x44
+ mirror: once
+
+# YAML+Python changes `.4455667` to `0.4455667` because it interprets the value
+# as a float.
+- name: my-dashed.atomic.float
+ type: AtomicFloat
+ value: .4455667
+ mirror: never
+ include: <math.h>
+"""
+
+# The corresponding code for good_input.
+good = {}
+
+good[
+ "static_pref_list_all_h"
+] = """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+
+#include "mozilla/StaticPrefList_my.h"
+#include "mozilla/StaticPrefList_my_dashed.h"
+"""
+
+good[
+ "static_prefs_all_h"
+] = """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+
+#include "mozilla/StaticPrefs_my.h"
+#include "mozilla/StaticPrefs_my_dashed.h"
+"""
+
+good["static_pref_list_group_h"] = {
+ "my": """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+
+NEVER_PREF("my.bool", bool, false)
+
+ONCE_PREF(
+ "my.int",
+ my_int,
+ my_int_AtStartup,
+ int32_t, -123
+)
+
+ALWAYS_PREF(
+ "my.uint",
+ my_uint,
+ my_uint,
+ uint32_t, 999
+)
+
+ONCE_PREF(
+ "my.float",
+ my_float,
+ my_float_AtStartup_DoNotUseDirectly,
+ float, 0.0f
+)
+
+NEVER_PREF("my.string", String, "foo\\"bar")
+
+NEVER_PREF("my.string2", String, "foobar")
+
+ALWAYS_PREF(
+ "my.atomic.bool",
+ my_atomic_bool,
+ my_atomic_bool,
+ RelaxedAtomicBool, true
+)
+
+ALWAYS_DATAMUTEX_PREF(
+ "my.datamutex.string",
+ my_datamutex_string,
+ my_datamutex_string,
+ DataMutexString, "foobar"_ns
+)
+
+ALWAYS_DATAMUTEX_PREF(
+ "my.datamutex.string.rust",
+ my_datamutex_string_rust,
+ my_datamutex_string_rust,
+ DataMutexString, ""_ns
+)
+
+ALWAYS_PREF(
+ "my.atomic.int",
+ my_atomic_int,
+ my_atomic_int_DoNotUseDirectly,
+ ReleaseAcquireAtomicInt32, 10 + 10 * 20
+)
+
+ONCE_PREF(
+ "my.atomic.uint",
+ my_atomic_uint,
+ my_atomic_uint_AtStartup,
+ SequentiallyConsistentAtomicUint32, 68
+)
+""",
+ "my_dashed": """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+
+NEVER_PREF("my-dashed.atomic.float", AtomicFloat, 0.4455667)
+""",
+}
+
+good["static_prefs_group_h"] = {
+ "my": """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+// Include it to gain access to StaticPrefs::my_*.
+
+#ifndef mozilla_StaticPrefs_my_h
+#define mozilla_StaticPrefs_my_h
+
+#include "foobar.h"
+
+#include "mozilla/StaticPrefListBegin.h"
+#include "mozilla/StaticPrefList_my.h"
+#include "mozilla/StaticPrefListEnd.h"
+
+#endif // mozilla_StaticPrefs_my_h
+"""
+}
+
+good[
+ "static_prefs_c_getters_cpp"
+] = """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+
+extern "C" uint32_t StaticPrefs_my_uint() {
+ return mozilla::StaticPrefs::my_uint();
+}
+
+extern "C" float StaticPrefs_my_float_AtStartup_DoNotUseDirectly() {
+ return mozilla::StaticPrefs::my_float_AtStartup_DoNotUseDirectly();
+}
+
+extern "C" bool StaticPrefs_my_atomic_bool() {
+ return mozilla::StaticPrefs::my_atomic_bool();
+}
+
+extern "C" void StaticPrefs_my_datamutex_string_rust(nsACString *result) {
+ const auto preflock = mozilla::StaticPrefs::my_datamutex_string_rust();
+ result->Append(*preflock);
+}
+"""
+
+good[
+ "static_prefs_rs"
+] = """\
+// This file was generated by generate_static_pref_list.py from (string input). DO NOT EDIT.
+
+pub use nsstring::nsCString;
+extern "C" {
+ pub fn StaticPrefs_my_uint() -> u32;
+ pub fn StaticPrefs_my_float_AtStartup_DoNotUseDirectly() -> f32;
+ pub fn StaticPrefs_my_atomic_bool() -> bool;
+ pub fn StaticPrefs_my_datamutex_string_rust(result: *mut nsstring::nsACString);
+}
+
+#[macro_export]
+macro_rules! pref {
+ ("my.uint") => (unsafe { $crate::StaticPrefs_my_uint() });
+ ("my.float") => (unsafe { $crate::StaticPrefs_my_float_AtStartup_DoNotUseDirectly() });
+ ("my.atomic.bool") => (unsafe { $crate::StaticPrefs_my_atomic_bool() });
+ ("my.datamutex.string.rust") => (unsafe { let mut result = $crate::nsCString::new(); $crate::StaticPrefs_my_datamutex_string_rust(&mut *result); result });
+}
+"""
+
+# A lot of bad inputs, each with an accompanying error message. Listed in order
+# of the relevant `error` calls within generate_static_pref_list.py.
+bad_inputs = [
+ (
+ """
+- invalidkey: 3
+""",
+ "invalid key `invalidkey`",
+ ),
+ (
+ """
+- type: int32_t
+""",
+ "missing `name` key",
+ ),
+ (
+ """
+- name: 99
+""",
+ "non-string `name` value `99`",
+ ),
+ (
+ """
+- name: name_with_no_dot
+""",
+ "`name` value `name_with_no_dot` lacks a '.'",
+ ),
+ (
+ """
+- name: pref.is.defined.more.than.once
+ type: bool
+ value: false
+ mirror: never
+- name: pref.is.defined.more.than.once
+ type: int32_t
+ value: 111
+ mirror: always
+""",
+ "`pref.is.defined.more.than.once` pref is defined more than once",
+ ),
+ (
+ """
+- name: your.pref
+ type: bool
+ value: false
+ mirror: never
+- name: my.pref
+ type: bool
+ value: false
+ mirror: never
+""",
+ "`my.pref` pref must come before `your.pref` pref",
+ ),
+ (
+ """
+- name: missing.type.key
+ value: false
+ mirror: never
+""",
+ "missing `type` key for pref `missing.type.key`",
+ ),
+ (
+ """
+- name: invalid.type.value
+ type: const char*
+ value: true
+ mirror: never
+""",
+ "invalid `type` value `const char*` for pref `invalid.type.value`",
+ ),
+ (
+ """
+- name: missing.value.key
+ type: int32_t
+ mirror: once
+""",
+ "missing `value` key for pref `missing.value.key`",
+ ),
+ (
+ """
+- name: non-string.value
+ type: String
+ value: 3.45
+ mirror: once
+""",
+ "non-string `value` value `3.45` for `String` pref `non-string.value`; add double quotes",
+ ),
+ (
+ """
+- name: invalid.boolean.value
+ type: bool
+ value: true || false
+ mirror: once
+""",
+ "invalid boolean value `true || false` for pref `invalid.boolean.value`",
+ ),
+ (
+ """
+- name: missing.mirror.key
+ type: int32_t
+ value: 3
+""",
+ "missing `mirror` key for pref `missing.mirror.key`",
+ ),
+ (
+ """
+- name: invalid.mirror.value
+ type: bool
+ value: true
+ mirror: sometimes
+""",
+ "invalid `mirror` value `sometimes` for pref `invalid.mirror.value`",
+ ),
+ (
+ """
+- name: non-boolean.do_not_use_directly.value
+ type: bool
+ value: true
+ mirror: always
+ do_not_use_directly: 0
+""",
+ "non-boolean `do_not_use_directly` value `0` for pref "
+ "`non-boolean.do_not_use_directly.value`",
+ ),
+ (
+ """
+- name: do_not_use_directly.uselessly.set
+ type: int32_t
+ value: 0
+ mirror: never
+ do_not_use_directly: true
+""",
+ "`do_not_use_directly` uselessly set with `mirror` value `never` for "
+ "pref `do_not_use_directly.uselessly.set`",
+ ),
+ (
+ """
+- name: non-string.include.value
+ type: bool
+ value: true
+ mirror: always
+ include: 33
+""",
+ "non-string `include` value `33` for pref `non-string.include.value`",
+ ),
+ (
+ """
+- name: include.value.starts.with
+ type: float
+ value: M_PI
+ mirror: never
+ include: <cmath
+""",
+ "`include` value `<cmath` starts with `<` but does not end with `>` for "
+ "pref `include.value.starts.with`",
+ ),
+ (
+ """
+- name: non-boolean.rust.value
+ type: bool
+ value: true
+ mirror: always
+ rust: 1
+""",
+ "non-boolean `rust` value `1` for pref `non-boolean.rust.value`",
+ ),
+ (
+ """
+- name: rust.uselessly.set
+ type: int32_t
+ value: 0
+ mirror: never
+ rust: true
+""",
+ "`rust` uselessly set with `mirror` value `never` for pref "
+ "`rust.uselessly.set`",
+ ),
+]
+
+
+class TestGenerateStaticPrefList(unittest.TestCase):
+ """
+ Unit tests for generate_static_pref_list.py.
+ """
+
+ def test_good(self):
+ "Test various pieces of good input."
+ inp = StringIO(good_input)
+ pref_list = yaml.safe_load(inp)
+ code = generate_code(pref_list, "(string input)")
+
+ self.assertEqual(good["static_pref_list_all_h"], code["static_pref_list_all_h"])
+
+ self.assertEqual(good["static_prefs_all_h"], code["static_prefs_all_h"])
+
+ self.assertEqual(
+ good["static_pref_list_group_h"]["my"],
+ code["static_pref_list_group_h"]["my"],
+ )
+ self.assertEqual(
+ good["static_pref_list_group_h"]["my_dashed"],
+ code["static_pref_list_group_h"]["my_dashed"],
+ )
+
+ self.assertEqual(
+ good["static_prefs_group_h"]["my"], code["static_prefs_group_h"]["my"]
+ )
+
+ self.assertEqual(
+ good["static_prefs_c_getters_cpp"], code["static_prefs_c_getters_cpp"]
+ )
+
+ self.assertEqual(good["static_prefs_rs"], code["static_prefs_rs"])
+
+ def test_bad(self):
+ "Test various pieces of bad input."
+
+ for (input_string, expected) in bad_inputs:
+ inp = StringIO(input_string)
+ try:
+ pref_list = yaml.safe_load(inp)
+ generate_code(pref_list, "(string input")
+ self.assertEqual(0, 1)
+ except ValueError as e:
+ self.assertEqual(str(e), expected)
+
+
+if __name__ == "__main__":
+ mozunit.main()
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]