diff options
Diffstat (limited to '')
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] |