Commit 038a72de authored by Nicholas Nethercote's avatar Nicholas Nethercote
Browse files

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

--HG--
extra : rebase_source : fa25bad87c4d9fcba6dc13cd2cc04ea6a2354f51
parent 74fea66c
Loading
Loading
Loading
Loading
+74 −36
Original line number Diff line number Diff line
@@ -596,8 +596,9 @@ public:

  nsresult SetDefaultValue(PrefType aType,
                           PrefValue aValue,
                           bool aFromFile,
                           bool aIsSticky,
                           bool aIsLocked,
                           bool aFromFile,
                           bool* aValueChanged)
  {
    // Types must always match when setting the default value.
@@ -607,7 +608,11 @@ public:

    // 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)) {
    if (!IsLocked()) {
      if (aIsLocked) {
        SetIsLocked(true);
      }
      if (!ValueMatches(PrefValueKind::Default, aType, aValue)) {
        mDefaultValue.Replace(Type(), aType, aValue);
        mHasDefaultValue = true;
        if (!aFromFile) {
@@ -622,6 +627,7 @@ public:
        // 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;
  }

@@ -944,6 +950,7 @@ pref_SetPref(const char* aPrefName,
             PrefValueKind aKind,
             PrefValue aValue,
             bool aIsSticky,
             bool aIsLocked,
             bool aFromFile)
{
  MOZ_ASSERT(NS_IsMainThread());
@@ -966,9 +973,10 @@ pref_SetPref(const char* aPrefName,
  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)) {
@@ -1076,7 +1084,8 @@ 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.
//
@@ -1087,6 +1096,7 @@ 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,
@@ -1100,13 +1110,14 @@ public:
  ~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;
    }
@@ -1128,11 +1139,17 @@ private:
                         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)
@@ -1164,7 +1181,8 @@ TestParseErrorHandlePref(const char* aPrefName,
                         PrefType aType,
                         PrefValueKind aKind,
                         PrefValue aValue,
                         bool aIsSticky)
                         bool aIsSticky,
                         bool aIsLocked)
{
}

@@ -1179,9 +1197,10 @@ TestParseErrorHandleError(const char* aMsg)

// 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,
@@ -2449,7 +2468,7 @@ Preferences::HandleDirty()
}

static nsresult
openPrefFile(nsIFile* aFile);
openPrefFile(nsIFile* aFile, PrefValueKind aKind);

static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
static const char kChannelPref[] = "app.update.channel";
@@ -3156,6 +3175,19 @@ Preferences::Observe(nsISupports* aSubject,
  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)
{
@@ -3166,7 +3198,7 @@ Preferences::ReadUserPrefsFromFile(nsIFile* aFile)
    return NS_ERROR_INVALID_ARG;
  }

  return openPrefFile(aFile);
  return openPrefFile(aFile, PrefValueKind::User);
}

NS_IMETHODIMP
@@ -3413,7 +3445,7 @@ Preferences::ReadSavedPrefs()
    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(
@@ -3442,7 +3474,7 @@ Preferences::ReadUserOverridePrefs()
  }

  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.
@@ -3584,7 +3616,7 @@ Preferences::WritePrefFile(nsIFile* aFile, SaveMethod aSaveMethod)
}

static nsresult
openPrefFile(nsIFile* aFile)
openPrefFile(nsIFile* aFile, PrefValueKind aKind)
{
  TimeStamp startTime = TimeStamp::Now();

@@ -3600,7 +3632,7 @@ openPrefFile(nsIFile* aFile)

  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;
  }

@@ -3701,7 +3733,7 @@ pref_LoadPrefsInDir(nsIFile* aDir,
  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;
@@ -3713,7 +3745,7 @@ pref_LoadPrefsInDir(nsIFile* aDir,
    // 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;
@@ -3734,7 +3766,11 @@ pref_ReadPrefFromJar(nsZipArchive* aJarReader, const char* aName)
              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;
  }

@@ -3814,7 +3850,7 @@ Preferences::InitInitialObjects()
    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?");
@@ -3943,15 +3979,14 @@ Preferences::InitInitialObjects()
  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);
@@ -4101,6 +4136,7 @@ Preferences::SetCStringInAnyProcess(const char* aPrefName,
                      aKind,
                      prefValue,
                      /* isSticky */ false,
                      /* isLocked */ false,
                      /* fromFile */ false);
}

@@ -4127,6 +4163,7 @@ Preferences::SetBoolInAnyProcess(const char* aPrefName,
                      aKind,
                      prefValue,
                      /* isSticky */ false,
                      /* isLocked */ false,
                      /* fromFile */ false);
}

@@ -4151,6 +4188,7 @@ Preferences::SetIntInAnyProcess(const char* aPrefName,
                      aKind,
                      prefValue,
                      /* isSticky */ false,
                      /* isLocked */ false,
                      /* fromFile */ false);
}

+9 −3
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ interface nsIPrefService : nsISupports
   *
   * @throws Error File failed to write.
   *
   * @see readUserPrefs
   * @see readUserPrefsFromFile
   * @see nsIFile
   */
  void savePrefFile(in nsIFile aFile);
@@ -102,14 +102,20 @@ interface nsIPrefService : nsISupports
  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);
};

+71 −21
Original line number Diff line number Diff line
@@ -4,10 +4,12 @@

//! 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>
@@ -21,6 +23,9 @@
//!   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
@@ -94,7 +99,7 @@ pub enum PrefType {
}

/// Keep this in sync with PrefValueKind in Preferences.h.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum PrefValueKind {
    Default,
@@ -112,7 +117,7 @@ pub union PrefValue {
/// 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);
@@ -129,8 +134,8 @@ type ErrorFn = unsafe extern "C" fn(msg: *const c_char);
/// 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
@@ -138,7 +143,7 @@ pub extern "C" fn prefs_parser_parse(path: *const c_char, buf: *const c_char, le
    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()
}

@@ -157,6 +162,8 @@ enum Token {
    UserPref,   // user_pref
    True,       // true
    False,      // false
    Sticky,     // sticky
    Locked,     // locked

    // String literal, e.g. '"string"'. The value is stored elsewhere.
    String,
@@ -272,17 +279,20 @@ struct KeywordInfo {
  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.
    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.
@@ -295,13 +305,15 @@ struct Parser<'t> {
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,
@@ -323,7 +335,7 @@ impl<'t> Parser<'t> {
        // 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),
@@ -370,7 +382,6 @@ impl<'t> Parser<'t> {
                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.
@@ -425,10 +436,49 @@ impl<'t> Parser<'t> {
                }
            };

            // ")"
            // ("," <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);
            }

            // ")"
            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;
            }

@@ -440,7 +490,7 @@ impl<'t> Parser<'t> {
            }

            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);
        }
+85 −50

File changed.

Preview size limit exceeded, changes collapsed.

+7 −2
Original line number Diff line number Diff line
// 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
@@ -50,6 +51,10 @@ pref
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);
Loading