Bug 440908 - Allow preference files to set locked prefs. r?njn draft
authorMike Hommey <mh+mozilla@glandium.org>
Fri, 24 Nov 2017 10:52:43 +0900
changeset 702934 0ef70a5c604ab2a96d3a694bdd4e450191a1d99a
parent 702789 0bb0f14672fdda31c19aea1ed829e050d693b9af
child 741605 1ce813f935cd335b3371233d2eee04509be6c578
push id90637
push userbmo:mh+mozilla@glandium.org
push dateFri, 24 Nov 2017 01:54:37 +0000
reviewersnjn
bugs440908
milestone59.0a1
Bug 440908 - Allow preference files to set locked prefs. r?njn
modules/libpref/Preferences.cpp
modules/libpref/test/unit/data/testPrefLocked.js
modules/libpref/test/unit/data/testPrefLockedUser.js
modules/libpref/test/unit/test_lockedprefs.js
modules/libpref/test/unit/xpcshell.ini
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -230,20 +230,23 @@ StrEscape(const char* aOriginal, nsCStri
   aResult.Append('"');
 }
 
 enum
 {
   kPrefSetDefault = 1,
   kPrefForceSet = 2,
   kPrefSticky = 4,
+  kPrefLocked = 8,
 };
 
 static ArenaAllocator<8192, 1> gPrefNameArena;
 
+static bool gIsAnyPrefLocked = false;
+
 class PrefHashEntry : public PLDHashEntryHdr
 {
 public:
   PrefHashEntry(const char* aName, PrefType aType)
   {
     mName = ArenaStrdup(aName, gPrefNameArena);
     SetType(aType);
     // We don't set the other fields because PLDHashTable always zeroes new
@@ -497,16 +500,20 @@ public:
           ReplaceValue(PrefValueKind::Default, aType, aValue);
           if (aFlags & kPrefSticky) {
             mIsSticky = true;
           }
           if (!mHasUserValue) {
             *aValueChanged = true;
           }
         }
+        if (aFlags & kPrefLocked) {
+          SetIsLocked(true);
+          gIsAnyPrefLocked = true;
+        }
         // What if we change the default to be the same as the user value?
         // Should we clear the user value?
       }
     } else {
       // If new value is same as the default value and it's not a "sticky"
       // pref, then un-set the user value. Otherwise, set the user value only
       // if it has changed.
       if (mHasDefaultValue && !mIsSticky &&
@@ -608,18 +615,16 @@ struct CallbackNode
 
 static PLDHashTable* gHashTable;
 
 // The callback list contains all the priority callbacks followed by the
 // non-priority callbacks. gLastPriorityNode records where the first part ends.
 static CallbackNode* gFirstCallback = nullptr;
 static CallbackNode* gLastPriorityNode = nullptr;
 
-static bool gIsAnyPrefLocked = false;
-
 // These are only used during the call to NotifyCallbacks().
 static bool gCallbacksInProgress = false;
 static bool gShouldCleanupDeadNodes = false;
 
 static PLDHashTableOps pref_HashTableOps = {
   PLDHashTable::HashStringKey,
   PrefHashEntry::MatchEntry,
   PLDHashTable::MoveEntryStub,
@@ -881,17 +886,18 @@ public:
   bool Parse(const char* aBuf, int aBufLen);
 
   bool GrowBuf();
 
   void HandleValue(const char* aPrefName,
                    PrefType aType,
                    PrefValue aValue,
                    bool aIsDefault,
-                   bool aIsSticky);
+                   bool aIsSticky,
+                   bool aIsLocked);
 
   void ReportProblem(const char* aMessage, int aLine, bool aError);
 
 private:
   // Pref parser states.
   enum class State
   {
     eInit,
@@ -915,16 +921,17 @@ private:
 
   static const int kUTF16EscapeNumDigits = 4;
   static const int kHexEscapeNumDigits = 2;
   static const int KBitsPerHexDigit = 4;
 
   static constexpr const char* kUserPref = "user_pref";
   static constexpr const char* kPref = "pref";
   static constexpr const char* kStickyPref = "sticky_pref";
+  static constexpr const char* kLockedPref = "locked_pref";
   static constexpr const char* kTrue = "true";
   static constexpr const char* kFalse = "false";
 
   State mState;           // current parse state
   State mNextState;       // sometimes used...
   const char* mStrMatch;  // string to match
   int mStrIndex;          // next char of smatch to check;
                           // also, counter in \u parsing
@@ -934,16 +941,17 @@ private:
   char mQuoteChar;        // char delimiter for quotations
   char* mLb;              // line buffer (only allocation)
   char* mLbCur;           // line buffer cursor
   char* mLbEnd;           // line buffer end
   char* mVb;              // value buffer (ptr into mLb)
   Maybe<PrefType> mVtype; // pref value type
   bool mIsDefault;        // true if (default) pref
   bool mIsSticky;         // true if (sticky) pref
+  bool mIsLocked;         // true if (locked) pref
 };
 
 // This function will increase the size of the buffer owned by the given pref
 // parse state. We currently use a simple doubling algorithm, but the only hard
 // requirement is that it increase the buffer by at least the size of the
 // mEscTmp buffer used for escape processing (currently 6 bytes).
 //
 // The buffer is used to store partial pref lines. It is freed when the parse
@@ -980,24 +988,28 @@ Parser::GrowBuf()
   return true;
 }
 
 void
 Parser::HandleValue(const char* aPrefName,
                     PrefType aType,
                     PrefValue aValue,
                     bool aIsDefault,
-                    bool aIsSticky)
+                    bool aIsSticky,
+                    bool aIsLocked)
 {
   uint32_t flags = 0;
   if (aIsDefault) {
     flags |= kPrefSetDefault;
     if (aIsSticky) {
       flags |= kPrefSticky;
     }
+    if (aIsLocked) {
+      flags |= kPrefLocked;
+    }
   } else {
     flags |= kPrefForceSet;
   }
   pref_SetPref(aPrefName, aValue, aType, flags);
 }
 
 // Report an error or a warning. If not specified, just dump to stderr.
 void
@@ -1060,31 +1072,35 @@ Parser::Parse(const char* aBuf, int aBuf
       // initial state
       case State::eInit:
         if (mLbCur != mLb) { // reset state
           mLbCur = mLb;
           mVb = nullptr;
           mVtype = Nothing();
           mIsDefault = false;
           mIsSticky = false;
+          mIsLocked = false;
         }
         switch (c) {
           case '/': // begin comment block or line?
             state = State::eCommentMaybeStart;
             break;
           case '#': // accept shell style comments
             state = State::eUntilEOL;
             break;
           case 'u': // indicating user_pref
           case 's': // indicating sticky_pref
           case 'p': // indicating pref
+          case 'l': // indicating locked_pref
             if (c == 'u') {
               mStrMatch = kUserPref;
             } else if (c == 's') {
               mStrMatch = kStickyPref;
+            } else if (c == 'l') {
+              mStrMatch = kLockedPref;
             } else {
               mStrMatch = kPref;
             }
             mStrIndex = 1;
             mNextState = State::eUntilOpenParen;
             state = State::eMatchString;
             break;
             // else skip char
@@ -1122,18 +1138,20 @@ Parser::Parse(const char* aBuf, int aBuf
         } else {
           *mLbCur++ = c;
         }
         break;
 
       // name parsing
       case State::eUntilName:
         if (c == '\"' || c == '\'') {
-          mIsDefault = (mStrMatch == kPref || mStrMatch == kStickyPref);
+          mIsDefault = (mStrMatch == kPref || mStrMatch == kStickyPref ||
+                        mStrMatch == kLockedPref);
           mIsSticky = (mStrMatch == kStickyPref);
+          mIsLocked = (mStrMatch == kLockedPref);
           mQuoteChar = c;
           mNextState = State::eUntilComma; // return here when done
           state = State::eQuotedString;
         } else if (c == '/') { // allow embedded comment
           mNextState = state;  // return here when done with comment
           state = State::eCommentMaybeStart;
         } else if (!isspace(c)) {
           ReportProblem("need space, comment or quote", lineNum, true);
@@ -1440,17 +1458,17 @@ Parser::Parse(const char* aBuf, int aBuf
               value.mBoolVal = (mVb == kTrue);
               break;
 
             default:
               MOZ_CRASH();
           }
 
           // We've extracted a complete name/value pair.
-          HandleValue(mLb, *mVtype, value, mIsDefault, mIsSticky);
+          HandleValue(mLb, *mVtype, value, mIsDefault, mIsSticky, mIsLocked);
 
           state = State::eInit;
         } else if (c == '/') {
           mNextState = state; // return here when done with comment
           state = State::eCommentMaybeStart;
         } else if (!isspace(c)) {
           ReportProblem("need space, comment or semicolon", lineNum, true);
           NS_WARNING("malformed pref file");
new file mode 100644
--- /dev/null
+++ b/modules/libpref/test/unit/data/testPrefLocked.js
@@ -0,0 +1,2 @@
+pref("testPref.unlocked.bool", true);
+locked_pref("testPref.locked.bool", false);
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.bool", true);
new file mode 100644
--- /dev/null
+++ b/modules/libpref/test/unit/test_lockedprefs.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/  */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.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=[]) {
+  ps.resetPrefs();
+  for (let filename of filenames) {
+    ps.readUserPrefsFromFile(do_get_file(filename));
+  }
+}
+
+add_test(function notChangedFromAPI() {
+  resetAndLoad(["data/testPrefLocked.js"]);
+  Assert.strictEqual(ps.getBoolPref("testPref.unlocked.bool"), true);
+  Assert.strictEqual(ps.getBoolPref("testPref.locked.bool"), false);
+
+  ps.setBoolPref("testPref.unlocked.bool", false);
+  Assert.ok(ps.prefHasUserValue("testPref.unlocked.bool"),
+            "should be able to set an unlocked pref");
+  Assert.strictEqual(ps.getBoolPref("testPref.unlocked.bool"), false);
+
+  ps.setBoolPref("testPref.locked.bool", true);
+  Assert.ok(ps.prefHasUserValue("testPref.locked.bool"),
+            "somehow, the user value is still set");
+  // But the user value is ignored on a get()
+  Assert.strictEqual(ps.getBoolPref("testPref.locked.bool"), false);
+  run_next_test();
+});
+
+add_test(function notChangedFromUserPrefs() {
+  resetAndLoad(["data/testPrefLocked.js", "data/testPrefLockedUser.js"]);
+  Assert.strictEqual(ps.getBoolPref("testPref.unlocked.bool"), true);
+  Assert.strictEqual(ps.getBoolPref("testPref.locked.bool"), false);
+
+  run_next_test();
+});
--- a/modules/libpref/test/unit/xpcshell.ini
+++ b/modules/libpref/test/unit/xpcshell.ini
@@ -6,13 +6,15 @@ 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_lockedprefs.js]
+support-files = data/testPrefLocked.js data/testPrefLockedUser.js
 [test_changeType.js]
 [test_defaultValues.js]
 [test_dirtyPrefs.js]
 [test_libPrefs.js]
 [test_bug1354613.js]