# HG changeset patch # User Nicholas Nethercote # Date 1519965100 -39600 # Node ID b82802657783b6188e320ba966183ae1eedabf62 # Parent 4d740690e6df9750224c544a1899cb64703029fd Bug 440908 - Add support for `sticky` and `locked` attributes to default prefs. r=glandium Sticky prefs are already specifiable with `sticky_pref`, but this is a more general attribute mechanism. The ability to specify a locked pref in the data file is new. The patch also adds nsIPrefService.readDefaultPrefsFromFile, to match the existing nsIPrefService.readUserPrefsFromFile method, and converts a number of the existing tests to use it. MozReview-Commit-ID: 9LLMBJVZfg7 diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -592,41 +592,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) @@ -940,16 +946,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; } @@ -962,19 +969,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", @@ -1063,62 +1071,71 @@ NotifyCallbacks(const char* aPrefName) 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 char* aPath, + bool Parse(PrefValueKind aKind, + const char* aPath, 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; } 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 console = do_GetService("@mozilla.org/consoleservice;1", &rv); if (NS_SUCCEEDED(rv)) { @@ -1140,34 +1157,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); @@ -2436,17 +2455,17 @@ Preferences::HandleDirty() sPreferences.get(), &Preferences::SavePrefFileAsynchronous), PREF_DELAY_MS); } } } static nsresult -openPrefFile(nsIFile* aFile); +openPrefFile(nsIFile* aFile, PrefValueKind aKind); static nsresult pref_LoadPrefsInDirList(const char* aListId); static const char kTelemetryPref[] = "toolkit.telemetry.enabled"; static const char kChannelPref[] = "app.update.channel"; // clang-format off @@ -3140,26 +3159,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); @@ -3299,17 +3331,17 @@ ReadExtensionPrefs(nsIFile* aFile) // Null-terminate the buffer. buf[read] = '\0'; nsCString buffer; buffer.Adopt(buf, read); Parser parser; - parser.Parse(entry.get(), buffer); + parser.Parse(PrefValueKind::Default, entry.get(), buffer); } return rv; } /* static */ void Preferences::SetPreference(const dom::Pref& aDomPref) { @@ -3460,17 +3492,17 @@ Preferences::ReadSavedPrefs() { nsCOMPtr 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). @@ -3489,17 +3521,17 @@ Preferences::ReadUserOverridePrefs() nsCOMPtr 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 @@ -3631,26 +3663,26 @@ 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) { nsCString data; MOZ_TRY_VAR(data, URLPreloader::ReadFile(aFile)); nsAutoString path; aFile->GetPath(path); Parser parser; - if (!parser.Parse(NS_ConvertUTF16toUTF8(path).get(), data)) { + if (!parser.Parse(aKind, NS_ConvertUTF16toUTF8(path).get(), data)) { return NS_ERROR_FILE_CORRUPTED; } return NS_OK; } static int pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /* unused */) @@ -3741,29 +3773,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; @@ -3815,17 +3847,17 @@ pref_LoadPrefsInDirList(const char* aLis static nsresult pref_ReadPrefFromJar(nsZipArchive* aJarReader, const char* aName) { nsCString manifest; MOZ_TRY_VAR(manifest, URLPreloader::ReadZip(aJarReader, nsDependentCString(aName))); Parser parser; - if (!parser.Parse(aName, manifest)) { + if (!parser.Parse(PrefValueKind::Default, aName, manifest)) { return NS_ERROR_FILE_CORRUPTED; } return NS_OK; } // Initialize default preference JavaScript buffers from appropriate TEXT // resources. @@ -3895,17 +3927,17 @@ Preferences::InitInitialObjects() // Load $gre/greprefs.js. nsCOMPtr 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 defaultPrefDir; @@ -4112,16 +4144,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) { @@ -4138,16 +4171,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); @@ -4162,16 +4196,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); diff --git a/modules/libpref/nsIPrefService.idl b/modules/libpref/nsIPrefService.idl --- 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, \ diff --git a/modules/libpref/parser/src/lib.rs b/modules/libpref/parser/src/lib.rs --- 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. //! //! = * -//! = "(" "," ")" ";" +//! = "(" "," ")" ";" //! = "user_pref" | "pref" | "sticky_pref" //! = //! = | "true" | "false" | //! = ? //! = "+" | "-" //! = [0-9]+ (and cannot be followed by [A-Za-z_]) //! = //! 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. +//! = ("," )* // in default pref files +//! = // in user pref files +//! = "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 { // - 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; } }; + // ("," )* // 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; + } + + // + 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; diff --git a/modules/libpref/test/gtest/Parser.cpp b/modules/libpref/test/gtest/Parser.cpp --- 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 } diff --git a/modules/libpref/test/unit/data/testParser.js b/modules/libpref/test/unit/data/testParser.js --- 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); diff --git a/modules/libpref/test/unit/data/testPrefLocked.js b/modules/libpref/test/unit/data/testPrefLocked.js new file mode 100644 --- /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 --- /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/test_locked_file_prefs.js b/modules/libpref/test/unit/test_locked_file_prefs.js new file mode 100644 --- /dev/null +++ b/modules/libpref/test/unit/test_locked_file_prefs.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +// This file tests the `locked` attribute in default pref files. + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +const ps = Services.prefs; + +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_parser.js b/modules/libpref/test/unit/test_parser.js --- a/modules/libpref/test/unit/test_parser.js +++ b/modules/libpref/test/unit/test_parser.js @@ -7,25 +7,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); diff --git a/modules/libpref/test/unit/test_stickyprefs.js b/modules/libpref/test/unit/test_stickyprefs.js --- a/modules/libpref/test/unit/test_stickyprefs.js +++ b/modules/libpref/test/unit/test_stickyprefs.js @@ -9,22 +9,27 @@ const ps = Services.prefs; // Once we fetch the profile directory the xpcshell test harness will send // a profile-before-change notification at shutdown. This causes the prefs // service to flush the prefs file - and the prefs file it uses ends up being // testPrefSticky*.js in the test dir. This upsets things in confusing ways :) // We avoid this by ensuring our "temp" prefs.js is the current prefs file. do_get_profile(); registerCleanupFunction(saveAndReload); -// 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(); @@ -42,17 +47,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"); @@ -64,58 +69,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"); @@ -124,28 +129,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) { @@ -156,17 +161,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); diff --git a/modules/libpref/test/unit/xpcshell.ini b/modules/libpref/test/unit/xpcshell.ini --- a/modules/libpref/test/unit/xpcshell.ini +++ b/modules/libpref/test/unit/xpcshell.ini @@ -6,16 +6,18 @@ support-files = [test_warnings.js] [test_bug345529.js] [test_bug506224.js] [test_bug577950.js] [test_bug790374.js] [test_stickyprefs.js] support-files = data/testPrefSticky.js data/testPrefStickyUser.js +[test_locked_file_prefs.js] +support-files = data/testPrefLocked.js data/testPrefLockedUser.js [test_changeType.js] [test_defaultValues.js] [test_dirtyPrefs.js] [test_extprefs.js] [test_libPrefs.js] [test_bug1354613.js] [test_parser.js] support-files = data/testParser.js diff --git a/services/sync/tests/unit/test_prefs_store.js b/services/sync/tests/unit/test_prefs_store.js --- 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();