--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -591,41 +591,47 @@ public:
}
mHasUserValue = false;
mHasChangedSinceInit = true;
}
nsresult SetDefaultValue(PrefType aType,
PrefValue aValue,
+ bool aIsSticky,
+ bool aIsLocked,
bool aFromFile,
- bool aIsSticky,
bool* aValueChanged)
{
// Types must always match when setting the default value.
if (!IsType(aType)) {
return NS_ERROR_UNEXPECTED;
}
// Should we set the default value? Only if the pref is not locked, and
// doing so would change the default value.
- if (!IsLocked() && !ValueMatches(PrefValueKind::Default, aType, aValue)) {
- mDefaultValue.Replace(Type(), aType, aValue);
- mHasDefaultValue = true;
- if (!aFromFile) {
- mHasChangedSinceInit = true;
+ if (!IsLocked()) {
+ if (aIsLocked) {
+ SetIsLocked(true);
}
- if (aIsSticky) {
- mIsSticky = true;
+ if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
+ mDefaultValue.Replace(Type(), aType, aValue);
+ mHasDefaultValue = true;
+ if (!aFromFile) {
+ mHasChangedSinceInit = true;
+ }
+ if (aIsSticky) {
+ mIsSticky = true;
+ }
+ if (!mHasUserValue) {
+ *aValueChanged = true;
+ }
+ // What if we change the default to be the same as the user value?
+ // Should we clear the user value? Currently we don't.
}
- if (!mHasUserValue) {
- *aValueChanged = true;
- }
- // What if we change the default to be the same as the user value?
- // Should we clear the user value? Currently we don't.
}
return NS_OK;
}
nsresult SetUserValue(PrefType aType,
PrefValue aValue,
bool aFromFile,
bool* aValueChanged)
@@ -939,16 +945,17 @@ pref_HashTableLookup(const char* aPrefNa
}
static nsresult
pref_SetPref(const char* aPrefName,
PrefType aType,
PrefValueKind aKind,
PrefValue aValue,
bool aIsSticky,
+ bool aIsLocked,
bool aFromFile)
{
MOZ_ASSERT(NS_IsMainThread());
if (!gHashTable) {
return NS_ERROR_OUT_OF_MEMORY;
}
@@ -961,19 +968,20 @@ pref_SetPref(const char* aPrefName,
if (pref->IsTypeNone()) {
// New entry. Set the type.
pref->SetType(aType);
}
bool valueChanged = false;
nsresult rv;
if (aKind == PrefValueKind::Default) {
- rv =
- pref->SetDefaultValue(aType, aValue, aFromFile, aIsSticky, &valueChanged);
+ rv = pref->SetDefaultValue(
+ aType, aValue, aIsSticky, aIsLocked, aFromFile, &valueChanged);
} else {
+ MOZ_ASSERT(!aIsLocked); // `locked` is disallowed in user pref files
rv = pref->SetUserValue(aType, aValue, aFromFile, &valueChanged);
}
if (NS_FAILED(rv)) {
NS_WARNING(
nsPrintfCString(
"Rejected attempt to change type of pref %s's %s value from %s to %s",
aPrefName,
(aKind == PrefValueKind::Default) ? "default" : "user",
@@ -1071,47 +1079,50 @@ static nsDataHashtable<nsCStringHashKey,
extern "C" {
// Keep this in sync with PrefFn in prefs_parser/src/lib.rs.
typedef void (*PrefsParserPrefFn)(const char* aPrefName,
PrefType aType,
PrefValueKind aKind,
PrefValue aValue,
- bool aIsSticky);
+ bool aIsSticky,
+ bool aIsLocked);
// Keep this in sync with ErrorFn in prefs_parser/src/lib.rs.
//
// `aMsg` is just a borrow of the string, and must be copied if it is used
// outside the lifetime of the prefs_parser_parse() call.
typedef void (*PrefsParserErrorFn)(const char* aMsg);
// Keep this in sync with prefs_parser_parse() in prefs_parser/src/lib.rs.
bool
prefs_parser_parse(const char* aPath,
+ PrefValueKind aKind,
const char* aBuf,
size_t aLen,
PrefsParserPrefFn aPrefFn,
PrefsParserErrorFn aErrorFn);
}
class Parser
{
public:
Parser() = default;
~Parser() = default;
bool Parse(const nsCString& aName,
+ PrefValueKind aKind,
const char* aPath,
const TimeStamp& aStartTime,
const nsCString& aBuf)
{
sNumPrefs = 0;
bool ok = prefs_parser_parse(
- aPath, aBuf.get(), aBuf.Length(), HandlePref, HandleError);
+ aPath, aKind, aBuf.get(), aBuf.Length(), HandlePref, HandleError);
if (!ok) {
return false;
}
uint32_t loadTime_us = (TimeStamp::Now() - aStartTime).ToMicroseconds();
// Most prefs files are read before telemetry initializes, so we have to
// save these measurements now and send them to telemetry later.
@@ -1123,21 +1134,27 @@ public:
return true;
}
private:
static void HandlePref(const char* aPrefName,
PrefType aType,
PrefValueKind aKind,
PrefValue aValue,
- bool aIsSticky)
+ bool aIsSticky,
+ bool aIsLocked)
{
sNumPrefs++;
- pref_SetPref(
- aPrefName, aType, aKind, aValue, aIsSticky, /* fromFile */ true);
+ pref_SetPref(aPrefName,
+ aType,
+ aKind,
+ aValue,
+ aIsSticky,
+ aIsLocked,
+ /* fromFile */ true);
}
static void HandleError(const char* aMsg)
{
nsresult rv;
nsCOMPtr<nsIConsoleService> console =
do_GetService("@mozilla.org/consoleservice;1", &rv);
if (NS_SUCCEEDED(rv)) {
@@ -1159,34 +1176,36 @@ uint32_t Parser::sNumPrefs = 0;
// The following code is test code for the gtest.
static void
TestParseErrorHandlePref(const char* aPrefName,
PrefType aType,
PrefValueKind aKind,
PrefValue aValue,
- bool aIsSticky)
+ bool aIsSticky,
+ bool aIsLocked)
{
}
static nsCString gTestParseErrorMsgs;
static void
TestParseErrorHandleError(const char* aMsg)
{
gTestParseErrorMsgs.Append(aMsg);
gTestParseErrorMsgs.Append('\n');
}
// Keep this in sync with the declaration in test/gtest/Parser.cpp.
void
-TestParseError(const char* aText, nsCString& aErrorMsg)
+TestParseError(PrefValueKind aKind, const char* aText, nsCString& aErrorMsg)
{
prefs_parser_parse("test",
+ aKind,
aText,
strlen(aText),
TestParseErrorHandlePref,
TestParseErrorHandleError);
// Copy the error messages into the outparam, then clear them from
// gTestParseErrorMsgs.
aErrorMsg.Assign(gTestParseErrorMsgs);
@@ -2444,17 +2463,17 @@ Preferences::HandleDirty()
sPreferences.get(),
&Preferences::SavePrefFileAsynchronous),
PREF_DELAY_MS);
}
}
}
static nsresult
-openPrefFile(nsIFile* aFile);
+openPrefFile(nsIFile* aFile, PrefValueKind aKind);
static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
static const char kChannelPref[] = "app.update.channel";
// clang-format off
static const char kPrefFileHeader[] =
"// Mozilla User Preferences"
NS_LINEBREAK
@@ -3152,26 +3171,39 @@ Preferences::Observe(nsISupports* aSubje
// from the suspended state, we save preferences before suspending.
rv = SavePrefFileBlocking();
}
return rv;
}
NS_IMETHODIMP
+Preferences::ReadDefaultPrefsFromFile(nsIFile* aFile)
+{
+ ENSURE_PARENT_PROCESS("Preferences::ReadDefaultPrefsFromFile", "all prefs");
+
+ if (!aFile) {
+ NS_ERROR("ReadDefaultPrefsFromFile requires a parameter");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return openPrefFile(aFile, PrefValueKind::Default);
+}
+
+NS_IMETHODIMP
Preferences::ReadUserPrefsFromFile(nsIFile* aFile)
{
ENSURE_PARENT_PROCESS("Preferences::ReadUserPrefsFromFile", "all prefs");
if (!aFile) {
NS_ERROR("ReadUserPrefsFromFile requires a parameter");
return NS_ERROR_INVALID_ARG;
}
- return openPrefFile(aFile);
+ return openPrefFile(aFile, PrefValueKind::User);
}
NS_IMETHODIMP
Preferences::ResetPrefs()
{
ENSURE_PARENT_PROCESS("Preferences::ResetPrefs", "all prefs");
gHashTable->ClearAndPrepareForLength(PREF_HASHTABLE_INITIAL_LENGTH);
@@ -3408,17 +3440,17 @@ Preferences::ReadSavedPrefs()
{
nsCOMPtr<nsIFile> file;
nsresult rv =
NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
- rv = openPrefFile(file);
+ rv = openPrefFile(file, PrefValueKind::User);
if (rv == NS_ERROR_FILE_NOT_FOUND) {
// This is a normal case for new users.
Telemetry::ScalarSet(
Telemetry::ScalarID::PREFERENCES_CREATED_NEW_USER_PREFS_FILE, true);
rv = NS_OK;
} else if (NS_FAILED(rv)) {
// Save a backup copy of the current (invalid) prefs file, since all prefs
// from the error line to the end of the file will be lost (bug 361102).
@@ -3437,17 +3469,17 @@ Preferences::ReadUserOverridePrefs()
nsCOMPtr<nsIFile> aFile;
nsresult rv =
NS_GetSpecialDirectory(NS_APP_PREFS_50_DIR, getter_AddRefs(aFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
aFile->AppendNative(NS_LITERAL_CSTRING("user.js"));
- rv = openPrefFile(aFile);
+ rv = openPrefFile(aFile, PrefValueKind::User);
if (rv != NS_ERROR_FILE_NOT_FOUND) {
// If the file exists and was at least partially read, record that in
// telemetry as it may be a sign of pref injection.
Telemetry::ScalarSet(Telemetry::ScalarID::PREFERENCES_READ_USER_JS, true);
}
}
nsresult
@@ -3579,33 +3611,33 @@ Preferences::WritePrefFile(nsIFile* aFil
// This will do a main thread write. It is safe to do it this way because
// AllowOffMainThreadSave() returns a consistent value for the lifetime of
// the parent process.
PrefSaveData prefsData = pref_savePrefs();
return PreferencesWriter::Write(aFile, prefsData);
}
static nsresult
-openPrefFile(nsIFile* aFile)
+openPrefFile(nsIFile* aFile, PrefValueKind aKind)
{
TimeStamp startTime = TimeStamp::Now();
nsCString data;
MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile));
nsAutoString filenameUtf16;
aFile->GetLeafName(filenameUtf16);
NS_ConvertUTF16toUTF8 filename(filenameUtf16);
nsAutoString path;
aFile->GetPath(path);
Parser parser;
if (!parser.Parse(
- filename, NS_ConvertUTF16toUTF8(path).get(), startTime, data)) {
+ filename, aKind, NS_ConvertUTF16toUTF8(path).get(), startTime, data)) {
return NS_ERROR_FILE_CORRUPTED;
}
return NS_OK;
}
static int
pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /* unused */)
@@ -3696,29 +3728,29 @@ pref_LoadPrefsInDir(nsIFile* aDir,
return rv;
}
prefFiles.Sort(pref_CompareFileNames, nullptr);
uint32_t arrayCount = prefFiles.Count();
uint32_t i;
for (i = 0; i < arrayCount; ++i) {
- rv2 = openPrefFile(prefFiles[i]);
+ rv2 = openPrefFile(prefFiles[i], PrefValueKind::Default);
if (NS_FAILED(rv2)) {
NS_ERROR("Default pref file not parsed successfully.");
rv = rv2;
}
}
arrayCount = specialFiles.Count();
for (i = 0; i < arrayCount; ++i) {
// This may be a sparse array; test before parsing.
nsIFile* file = specialFiles[i];
if (file) {
- rv2 = openPrefFile(file);
+ rv2 = openPrefFile(file, PrefValueKind::Default);
if (NS_FAILED(rv2)) {
NS_ERROR("Special default pref file not parsed successfully.");
rv = rv2;
}
}
}
return rv;
@@ -3729,17 +3761,21 @@ pref_ReadPrefFromJar(nsZipArchive* aJarR
{
TimeStamp startTime = TimeStamp::Now();
nsCString manifest;
MOZ_TRY_VAR(manifest,
URLPreloader::ReadZip(aJarReader, nsDependentCString(aName)));
Parser parser;
- if (!parser.Parse(nsDependentCString(aName), aName, startTime, manifest)) {
+ if (!parser.Parse(nsDependentCString(aName),
+ PrefValueKind::Default,
+ aName,
+ startTime,
+ manifest)) {
return NS_ERROR_FILE_CORRUPTED;
}
return NS_OK;
}
// Initialize default preference JavaScript buffers from appropriate TEXT
// resources.
@@ -3809,17 +3845,17 @@ Preferences::InitInitialObjects()
// Load $gre/greprefs.js.
nsCOMPtr<nsIFile> greprefsFile;
rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile));
NS_ENSURE_SUCCESS(rv, Err("NS_GetSpecialDirectory(NS_GRE_DIR) failed"));
rv = greprefsFile->AppendNative(NS_LITERAL_CSTRING("greprefs.js"));
NS_ENSURE_SUCCESS(rv, Err("greprefsFile->AppendNative() failed"));
- rv = openPrefFile(greprefsFile);
+ rv = openPrefFile(greprefsFile, PrefValueKind::Default);
if (NS_FAILED(rv)) {
NS_WARNING("Error parsing GRE default preferences. Is this an old-style "
"embedding app?");
}
}
// Load $gre/defaults/pref/*.js.
nsCOMPtr<nsIFile> defaultPrefDir;
@@ -3938,25 +3974,24 @@ Preferences::InitInitialObjects()
developerBuild = !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "default");
#endif
// Release Candidate builds are builds that think they are release builds, but
// are shipped to beta users. We still need extended data from these users.
bool releaseCandidateOnBeta = false;
if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "release")) {
nsAutoCString updateChannelPrefValue;
- Preferences::GetCString(kChannelPref, updateChannelPrefValue,
- PrefValueKind::Default);
+ Preferences::GetCString(
+ kChannelPref, updateChannelPrefValue, PrefValueKind::Default);
releaseCandidateOnBeta = updateChannelPrefValue.EqualsLiteral("beta");
}
if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "nightly") ||
!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "aurora") ||
- !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta") ||
- developerBuild ||
+ !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta") || developerBuild ||
releaseCandidateOnBeta) {
Preferences::SetBoolInAnyProcess(
kTelemetryPref, true, PrefValueKind::Default);
} else {
Preferences::SetBoolInAnyProcess(
kTelemetryPref, false, PrefValueKind::Default);
}
Preferences::LockInAnyProcess(kTelemetryPref);
@@ -4096,16 +4131,17 @@ Preferences::SetCStringInAnyProcess(cons
PrefValue prefValue;
const nsCString& flat = PromiseFlatCString(aValue);
prefValue.mStringVal = flat.get();
return pref_SetPref(aPrefName,
PrefType::String,
aKind,
prefValue,
/* isSticky */ false,
+ /* isLocked */ false,
/* fromFile */ false);
}
/* static */ nsresult
Preferences::SetCString(const char* aPrefName,
const nsACString& aValue,
PrefValueKind aKind)
{
@@ -4122,16 +4158,17 @@ Preferences::SetBoolInAnyProcess(const c
PrefValue prefValue;
prefValue.mBoolVal = aValue;
return pref_SetPref(aPrefName,
PrefType::Bool,
aKind,
prefValue,
/* isSticky */ false,
+ /* isLocked */ false,
/* fromFile */ false);
}
/* static */ nsresult
Preferences::SetBool(const char* aPrefName, bool aValue, PrefValueKind aKind)
{
ENSURE_PARENT_PROCESS("SetBool", aPrefName);
return SetBoolInAnyProcess(aPrefName, aValue, aKind);
@@ -4146,16 +4183,17 @@ Preferences::SetIntInAnyProcess(const ch
PrefValue prefValue;
prefValue.mIntVal = aValue;
return pref_SetPref(aPrefName,
PrefType::Int,
aKind,
prefValue,
/* isSticky */ false,
+ /* isLocked */ false,
/* fromFile */ false);
}
/* static */ nsresult
Preferences::SetInt(const char* aPrefName, int32_t aValue, PrefValueKind aKind)
{
ENSURE_PARENT_PROCESS("SetInt", aPrefName);
return SetIntInAnyProcess(aPrefName, aValue, aKind);
--- a/modules/libpref/nsIPrefService.idl
+++ b/modules/libpref/nsIPrefService.idl
@@ -40,17 +40,17 @@ interface nsIPrefService : nsISupports
* @param aFile The file to be written.
*
* @note
* If nullptr is passed in for the aFile parameter the preference data is
* written out to the current preferences file (usually prefs.js.)
*
* @throws Error File failed to write.
*
- * @see readUserPrefs
+ * @see readUserPrefsFromFile
* @see nsIFile
*/
void savePrefFile(in nsIFile aFile);
/**
* Call to get a Preferences "Branch" which accesses user preference data.
* Using a Set method on this object will always create or set a user
* preference value. When using a Get method a user set value will be
@@ -97,24 +97,30 @@ interface nsIPrefService : nsISupports
/**
* The preference service is 'dirty' if there are changes to user preferences
* that have not been written to disk
*/
readonly attribute boolean dirty;
/**
- * Read in the preferences specified in a user preference file. This method
- * does not clear user preferences that were already set.
+ * Read in the preferences specified in a default preference file. This
+ * method does not clear preferences that were already set, but it may
+ * overwrite existing preferences.
*
* @param aFile The file to be read.
*
* @throws Error File failed to read or contained invalid data.
* @note This method is intended for internal unit testing only!
*/
+ void readDefaultPrefsFromFile(in nsIFile aFile);
+
+ /**
+ * Like readDefaultPrefsFromFile, but for a user prefs file.
+ */
void readUserPrefsFromFile(in nsIFile aFile);
};
%{C++
#define NS_PREFSERVICE_CID \
{ /* {1cd91b88-1dd2-11b2-92e1-ed22ed298000} */ \
0x91ca2441, \
--- a/modules/libpref/parser/src/lib.rs
+++ b/modules/libpref/parser/src/lib.rs
@@ -1,31 +1,36 @@
/* 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/. */
//! This crate implements a prefs file parser.
//!
-//! Pref files have the following grammar.
+//! Pref files have the following grammar. Note that there are slight
+//! differences between the grammar for a default prefs files and a user prefs
+//! file.
//!
//! <pref-file> = <pref>*
-//! <pref> = <pref-spec> "(" <pref-name> "," <pref-value> ")" ";"
+//! <pref> = <pref-spec> "(" <pref-name> "," <pref-value> <pref-attrs> ")" ";"
//! <pref-spec> = "user_pref" | "pref" | "sticky_pref"
//! <pref-name> = <string-literal>
//! <pref-value> = <string-literal> | "true" | "false" | <int-value>
//! <int-value> = <sign>? <int-literal>
//! <sign> = "+" | "-"
//! <int-literal> = [0-9]+ (and cannot be followed by [A-Za-z_])
//! <string-literal> =
//! A single or double-quoted string, with the following escape sequences
//! allowed: \", \', \\, \n, \r, \xNN, \uNNNN, where \xNN gives a raw byte
//! value that is copied directly into an 8-bit string value, and \uNNNN
//! gives a UTF-16 code unit that is converted to UTF-8 before being copied
//! into an 8-bit string value. \x00 and \u0000 are disallowed because they
//! would cause C++ code handling such strings to misbehave.
+//! <pref-attrs> = ("," <pref-attr>)* // in default pref files
+//! = <empty> // in user pref files
+//! <pref-attr> = "sticky" | "locked" // default pref files only
//!
//! Comments can take three forms:
//! - # Python-style comments
//! - // C++ style comments
//! - /* C style comments (non-nested) */
//!
//! Non-end-of-line whitespace chars are \t, \v, \f, and space.
//!
@@ -89,17 +94,17 @@ use std::os::raw::{c_char, c_uchar};
pub enum PrefType {
None,
String,
Int,
Bool,
}
/// Keep this in sync with PrefValueKind in Preferences.h.
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum PrefValueKind {
Default,
User
}
/// Keep this in sync with PrefValue in Preferences.cpp.
#[repr(C)]
@@ -107,43 +112,43 @@ pub union PrefValue {
string_val: *const c_char,
int_val: i32,
bool_val: bool,
}
/// Keep this in sync with PrefsParserPrefFn in Preferences.cpp.
type PrefFn = unsafe extern "C" fn(pref_name: *const c_char, pref_type: PrefType,
pref_value_kind: PrefValueKind, pref_value: PrefValue,
- is_sticky: bool);
+ is_sticky: bool, is_locked: bool);
/// Keep this in sync with PrefsParserErrorFn in Preferences.cpp.
type ErrorFn = unsafe extern "C" fn(msg: *const c_char);
/// Parse the contents of a prefs file.
///
/// `buf` is a null-terminated string. `len` is its length, excluding the
/// null terminator.
///
/// `pref_fn` is called once for each successfully parsed pref.
///
/// `error_fn` is called once for each parse error detected.
///
/// Keep this in sync with the prefs_parser_parse() declaration in
/// Preferences.cpp.
#[no_mangle]
-pub extern "C" fn prefs_parser_parse(path: *const c_char, buf: *const c_char, len: usize,
- pref_fn: PrefFn, error_fn: ErrorFn) -> bool {
+pub extern "C" fn prefs_parser_parse(path: *const c_char, kind: PrefValueKind, buf: *const c_char,
+ len: usize, pref_fn: PrefFn, error_fn: ErrorFn) -> bool {
let path = unsafe { std::ffi::CStr::from_ptr(path).to_string_lossy().into_owned() };
// Make sure `buf` ends in a '\0', and include that in the length, because
// it represents EOF.
let buf = unsafe { std::slice::from_raw_parts(buf as *const c_uchar, len + 1) };
assert!(buf.last() == Some(&EOF));
- let mut parser = Parser::new(&path, &buf, pref_fn, error_fn);
+ let mut parser = Parser::new(&path, kind, &buf, pref_fn, error_fn);
parser.parse()
}
//---------------------------------------------------------------------------
// The implementation
//---------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -152,16 +157,18 @@ enum Token {
SingleChar(u8),
// Keywords
Pref, // pref
StickyPref, // sticky_pref
UserPref, // user_pref
True, // true
False, // false
+ Sticky, // sticky
+ Locked, // locked
// String literal, e.g. '"string"'. The value is stored elsewhere.
String,
// Unsigned integer literal, e.g. '123'. Although libpref uses i32 values,
// any '-' and '+' before an integer literal are treated as separate
// tokens, so these token values are always positive. Furthermore, we
// tokenize int literals as u32 so that 2147483648 (which doesn't fit into
@@ -267,46 +274,51 @@ const SPECIAL_STRING_CHARS: [bool; 256]
/* 250+ */ _______, _______, _______, _______, _______, _______
];
struct KeywordInfo {
string: &'static [u8],
token: Token,
}
-const KEYWORD_INFOS: &[KeywordInfo; 5] = &[
+const KEYWORD_INFOS: &[KeywordInfo; 7] = &[
// These are ordered by frequency.
KeywordInfo { string: b"pref", token: Token::Pref },
KeywordInfo { string: b"true", token: Token::True },
KeywordInfo { string: b"false", token: Token::False },
KeywordInfo { string: b"user_pref", token: Token::UserPref },
+ KeywordInfo { string: b"sticky", token: Token::Sticky },
+ KeywordInfo { string: b"locked", token: Token::Locked },
KeywordInfo { string: b"sticky_pref", token: Token::StickyPref },
];
struct Parser<'t> {
- path: &'t str, // Path to the file being parsed. Used in error messages.
- buf: &'t [u8], // Text being parsed.
- i: usize, // Index of next char to be read.
- line_num: u32, // Current line number within the text.
- pref_fn: PrefFn, // Callback for processing each pref.
- error_fn: ErrorFn, // Callback for parse errors.
- has_errors: bool, // Have we encountered errors?
+ path: &'t str, // Path to the file being parsed. Used in error messages.
+ kind: PrefValueKind, // Default prefs file or user prefs file?
+ buf: &'t [u8], // Text being parsed.
+ i: usize, // Index of next char to be read.
+ line_num: u32, // Current line number within the text.
+ pref_fn: PrefFn, // Callback for processing each pref.
+ error_fn: ErrorFn, // Callback for parse errors.
+ has_errors: bool, // Have we encountered errors?
}
// As described above, we use 0 to represent EOF.
const EOF: u8 = b'\0';
impl<'t> Parser<'t> {
- fn new(path: &'t str, buf: &'t [u8], pref_fn: PrefFn, error_fn: ErrorFn) -> Parser<'t> {
+ fn new(path: &'t str, kind: PrefValueKind, buf: &'t [u8], pref_fn: PrefFn, error_fn: ErrorFn)
+ -> Parser<'t> {
// Make sure these tables take up 1 byte per entry.
assert!(std::mem::size_of_val(&CHAR_KINDS) == 256);
assert!(std::mem::size_of_val(&SPECIAL_STRING_CHARS) == 256);
Parser {
path: path,
+ kind: kind,
buf: buf,
i: 0,
line_num: 1,
pref_fn: pref_fn,
error_fn: error_fn,
has_errors: false,
}
}
@@ -318,17 +330,17 @@ impl<'t> Parser<'t> {
let mut none_str = Vec::with_capacity(0); // For tokens that shouldn't be strings.
let mut token = self.get_token(&mut none_str);
// At the top of the loop we already have a token. In a valid input
// this will be either the first token of a new pref, or EOF.
loop {
// <pref-spec>
- let (pref_value_kind, is_sticky) = match token {
+ let (pref_value_kind, mut is_sticky) = match token {
Token::Pref => (PrefValueKind::Default, false),
Token::StickyPref => (PrefValueKind::Default, true),
Token::UserPref => (PrefValueKind::User, false),
Token::SingleChar(EOF) => return !self.has_errors,
_ => {
token = self.error_and_recover(
token, "expected pref specifier at start of pref definition");
continue;
@@ -365,17 +377,16 @@ impl<'t> Parser<'t> {
(PrefType::Bool, PrefValue { bool_val: true })
}
Token::False => {
(PrefType::Bool, PrefValue { bool_val: false })
}
Token::String => {
(PrefType::String,
PrefValue { string_val: value_str.as_ptr() as *const c_char })
-
}
Token::Int(u) => {
// Accept u <= 2147483647; anything larger will overflow i32.
if u <= std::i32::MAX as u32 {
(PrefType::Int, PrefValue { int_val: u as i32 })
} else {
token = self.error_and_recover(
Token::Error("integer literal overflowed"), "");
@@ -420,32 +431,71 @@ impl<'t> Parser<'t> {
}
_ => {
token = self.error_and_recover(token, "expected pref value after ','");
continue;
}
};
+ // ("," <pref-attr>)* // default pref files only
+ let mut is_locked = false;
+ let mut has_attrs = false;
+ if self.kind == PrefValueKind::Default {
+ let ok = loop {
+ // ","
+ token = self.get_token(&mut none_str);
+ if token != Token::SingleChar(b',') {
+ break true;
+ }
+
+ // <pref-attr>
+ token = self.get_token(&mut none_str);
+ match token {
+ Token::Sticky => is_sticky = true,
+ Token::Locked => is_locked = true,
+ _ => {
+ token =
+ self.error_and_recover(token, "expected pref attribute after ','");
+ break false;
+ }
+ }
+ has_attrs = true;
+ };
+ if !ok {
+ continue;
+ }
+ } else {
+ token = self.get_token(&mut none_str);
+ }
+
// ")"
- token = self.get_token(&mut none_str);
if token != Token::SingleChar(b')') {
- token = self.error_and_recover(token, "expected ')' after pref value");
+ let expected_msg = if self.kind == PrefValueKind::Default {
+ if has_attrs {
+ "expected ',' or ')' after pref attribute"
+ } else {
+ "expected ',' or ')' after pref value"
+ }
+ } else {
+ "expected ')' after pref value"
+ };
+ token = self.error_and_recover(token, expected_msg);
continue;
}
// ";"
token = self.get_token(&mut none_str);
if token != Token::SingleChar(b';') {
token = self.error_and_recover(token, "expected ';' after ')'");
continue;
}
unsafe { (self.pref_fn)(pref_name.as_ptr() as *const c_char, pref_type, pref_value_kind,
- pref_value, is_sticky) };
+ pref_value, is_sticky, is_locked) };
token = self.get_token(&mut none_str);
}
}
fn error_and_recover(&mut self, token: Token, msg: &str) -> Token {
self.has_errors = true;
--- a/modules/libpref/test/gtest/Parser.cpp
+++ b/modules/libpref/test/gtest/Parser.cpp
@@ -1,71 +1,80 @@
/* -*- 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(const char* aText, nsCString& aErrorMsg);
+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(text_, expectedErrorMsg_) \
+#define P(kind_, text_, expectedErrorMsg_) \
do { \
- TestParseError(text_, actualErrorMsg); \
+ 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.
- P(R"(
+ DEFAULT(R"(
pref("bool", true);
sticky_pref("int", 123);
user_pref("string", "value");
)",
""
);
// Totally empty input.
- P("",
+ DEFAULT("",
""
);
// Whitespace-only input.
- P(R"(
+ DEFAULT(R"(
)" "\v \t \v \f",
""
);
//-------------------------------------------------------------------------
// 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.
- P(R"(
+ 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);
@@ -79,146 +88,146 @@ pref("int.overflow", 1234567890987654321
"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.
- P(R"(
+ 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.
- P(R"(
+ 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.
- P(R"(
+ 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.
- P(R"(
+ 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.
- P(R"(
+ 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.)
- P(R"(
+ 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.
- P(R"(
+ 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.
- P(R"(
+ 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.
- P(R"(
+ 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.)
- P(R"(
+ 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 value.
// (The string literal is broken in two so that MSVC doesn't complain about
// an invalid universal-character-name.)
- P(R"(
+ DEFAULT(R"(
pref("string.bad-u-surrogate", "foo\)" R"(ud83c\u1234");
pref("int.ok", 0);
)",
"test:2: prefs parse error: invalid low surrogate value after high surrogate\n"
);
// Unlike in JavaScript, \b, \f, \t, \v aren't allowed.
- P(R"(
+ 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.
- P(R"(
+ 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"
@@ -227,176 +236,202 @@ pref("string.bad-escape", "foo\)",
"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.
- P(R"(
+ 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.
- P(R"(
+ DEFAULT(R"(
pref("string.unterminated-string", "foo);
pref("int.ok", 0);
)",
"test:3: prefs parse error: unknown keyword\n"
);
// Mismatched quotes (1).
- P(R"(
+ DEFAULT(R"(
pref("string.unterminated-string", "foo');
)",
"test:3: prefs parse error: unterminated string literal\n"
);
// Mismatched quotes (2).
- P(R"(
+ DEFAULT(R"(
pref("string.unterminated-string", 'foo");
)",
"test:3: prefs parse error: unterminated string literal\n"
);
// Unknown keywords.
- P(R"(
+ 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.
- P(R"(
+ DEFAULT(R"(
/* comment
)",
"test:3: prefs parse error: unterminated /* comment\n"
);
// Malformed comment.
- P(R"(
+ DEFAULT(R"(
/ comment
)",
"test:2: prefs parse error: expected '/' or '*' after '/'\n"
);
// C++-style comment ending in EOF (1).
- P(R"(
+ DEFAULT(R"(
// comment)",
""
);
// C++-style comment ending in EOF (2).
- P(R"(
+ DEFAULT(R"(
//)",
""
);
// Various unexpected characters.
- P(R"(
+ 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.
//-------------------------------------------------------------------------
- P(R"(
+ 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 ')' after pref value\n"
- "test:11: prefs parse error: expected ';' after ')'\n"
- "test:12: prefs parse error: expected ';' 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;
+pref("int.ok", 1);
+ )",
+ "test:2: prefs parse error: expected ')' after pref value\n"
);
// Parse errors involving unexpected EOF.
- P(R"(
+ DEFAULT(R"(
pref)",
"test:2: prefs parse error: expected '(' after pref specifier\n"
);
- P(R"(
+ DEFAULT(R"(
pref()",
"test:2: prefs parse error: expected pref name after '('\n"
);
- P(R"(
+ DEFAULT(R"(
pref("parse.error")",
"test:2: prefs parse error: expected ',' after pref name\n"
);
- P(R"(
+ DEFAULT(R"(
pref("parse.error",)",
"test:2: prefs parse error: expected pref value after ','\n"
);
- P(R"(
+ DEFAULT(R"(
pref("parse.error", -)",
"test:2: prefs parse error: expected integer literal after '-'\n"
);
- P(R"(
+ DEFAULT(R"(
pref("parse.error", +)",
"test:2: prefs parse error: expected integer literal after '+'\n"
);
- P(R"(
+ DEFAULT(R"(
+pref("parse.error", true)",
+ "test:2: prefs parse error: expected ',' or ')' after pref value\n"
+ );
+
+ USER(R"(
pref("parse.error", true)",
"test:2: prefs parse error: expected ')' after pref value\n"
);
- P(R"(
+ 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.
- P(R"(
+ DEFAULT(R"(
pref("parse.error", true);;
-pref("parse.error", true);;;
-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"
@@ -406,31 +441,31 @@ pref("int.ok", 0);
// 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.)
- P("\n \r \r\n bad",
+ DEFAULT("\n \r \r\n bad",
"test:4: prefs parse error: unknown keyword\n"
);
- P("#\n#\r#\r\n bad",
+ DEFAULT("#\n#\r#\r\n bad",
"test:4: prefs parse error: unknown keyword\n"
);
- P("//\n//\r//\r\n bad",
+ DEFAULT("//\n//\r//\r\n bad",
"test:4: prefs parse error: unknown keyword\n"
);
- P("/*\n \r \r\n*/ bad",
+ DEFAULT("/*\n \r \r\n*/ bad",
"test:4: prefs parse error: unknown keyword\n"
);
// Note: the escape sequences do *not* affect the line number.
- P("pref(\"foo\\n\n foo\\r\r foo\\r\\n\r\n foo\", bad);",
+ 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
}
--- a/modules/libpref/test/unit/data/testParser.js
+++ b/modules/libpref/test/unit/data/testParser.js
@@ -1,10 +1,11 @@
-// Note: this file tests only valid syntax. See
-// modules/libpref/test/gtest/Parser.cpp for tests if invalid syntax.
+// Note: this file tests only valid syntax (of user pref files, not default
+// pref files). See modules/libpref/test/gtest/Parser.cpp for tests if invalid
+// syntax.
#
# comment
# comment £
//
// comment
// comment £
/**/
@@ -45,16 +46,20 @@ pref
,
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);
--- a/modules/libpref/test/unit/test_parser.js
+++ b/modules/libpref/test/unit/test_parser.js
@@ -5,25 +5,30 @@ function run_test() {
const PREF_NAME = "testPref";
var ps = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService);
var defaultPrefs = ps.getDefaultBranch(null);
var prefs = ps.getBranch(null);
ps.resetPrefs();
- ps.readUserPrefsFromFile(do_get_file('data/testParser.js'));
+ 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);
--- a/modules/libpref/test/unit/test_stickyprefs.js
+++ b/modules/libpref/test/unit/test_stickyprefs.js
@@ -1,22 +1,27 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
const ps = Services.prefs;
-// A little helper to reset the service and load some pref files
-function resetAndLoad(filenames) {
+// A little helper to reset the service and load one pref file.
+function resetAndLoadDefaults() {
ps.resetPrefs();
- for (let filename of filenames) {
- ps.readUserPrefsFromFile(do_get_file(filename));
- }
+ 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();
@@ -34,17 +39,17 @@ function saveAndReload() {
}
function run_test() {
run_next_test();
}
// A sticky pref should not be written if the value is unchanged.
add_test(function notWrittenWhenUnchanged() {
- resetAndLoad(["data/testPrefSticky.js"]);
+ 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");
@@ -56,58 +61,58 @@ add_test(function notWrittenWhenUnchange
});
// 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.
- resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]);
+ 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.
- resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]);
+ 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.
- resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]);
+ 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.
- resetAndLoad(["data/testPrefSticky.js"]);
+ 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");
@@ -116,28 +121,28 @@ add_test(function hasUserValue() {
"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.
- resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]);
+ 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.
- resetAndLoad(["data/testPrefSticky.js", "data/testPrefStickyUser.js"]);
+ 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) {
@@ -148,17 +153,17 @@ add_test(function clearUserPref() {
// 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.
- resetAndLoad(["data/testPrefSticky.js"]);
+ 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);
--- a/services/sync/tests/unit/test_prefs_store.js
+++ b/services/sync/tests/unit/test_prefs_store.js
@@ -20,17 +20,17 @@ function makePersona(id) {
name: Math.random().toString(),
headerURL: "http://localhost:1234/a"
};
}
add_task(async function run_test() {
_("Test fixtures.");
// read our custom prefs file before doing anything.
- Services.prefs.readUserPrefsFromFile(do_get_file("prefs_test_prefs_store.js"));
+ Services.prefs.readDefaultPrefsFromFile(do_get_file("prefs_test_prefs_store.js"));
let engine = Service.engineManager.get("prefs");
let store = engine._store;
let prefs = new Preferences();
try {
_("The GUID corresponds to XUL App ID.");
let allIDs = await store.getAllIDs();