Merge mozilla-central to autoland. on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Wed, 07 Mar 2018 17:19:04 +0200
changeset 461989 32f170aa420a46b7036b3445795a9ad8d3122ed6
parent 461988 a2363b01e4eda903f5218052adab44cc6a53e540 (current diff)
parent 461945 493e45400842b6ccfffb63b58b40b33a0b8154ab (diff)
child 461990 7faa8401a434edb15ace92b33f3a1c7e5f853360
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. on a CLOSED TREE
browser/app/profile/firefox.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1236,19 +1236,20 @@ pref("services.sync.prefs.sync.xpinstall
 // A preference that controls whether we should show the icon for a remote tab.
 // This pref has no UI but exists because some people may be concerned that
 // fetching these icons to show remote tabs may leak information about that
 // user's tabs and bookmarks. Note this pref is also synced.
 pref("services.sync.syncedTabs.showRemoteIcons", true);
 
 // Developer edition preferences
 #ifdef MOZ_DEV_EDITION
-sticky_pref("lightweightThemes.selectedThemeID", "firefox-compact-dark@mozilla.org");
+pref("lightweightThemes.selectedThemeID", "firefox-compact-dark@mozilla.org",
+     sticky);
 #else
-sticky_pref("lightweightThemes.selectedThemeID", "");
+pref("lightweightThemes.selectedThemeID", "", sticky);
 #endif
 
 // Whether the character encoding menu is under the main Firefox button. This
 // preference is a string so that localizers can alter it.
 pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
 
 // Allow using tab-modal prompts when possible.
 pref("prompts.tab_modal.enabled", true);
--- a/devtools/shim/devtools-startup-prefs.js
+++ b/devtools/shim/devtools-startup-prefs.js
@@ -8,19 +8,19 @@
 // process in JS, they can be defined in this file.
 // Note that this preference file follows Firefox release cycle.
 
 // Enable the JSON View tool (an inspector for application/json documents).
 pref("devtools.jsonview.enabled", true);
 
 // Default theme ("dark" or "light")
 #ifdef MOZ_DEV_EDITION
-sticky_pref("devtools.theme", "dark");
+pref("devtools.theme", "dark", sticky);
 #else
-sticky_pref("devtools.theme", "light");
+pref("devtools.theme", "light", sticky);
 #endif
 
 // Should the devtools toolbar be opened on startup
 pref("devtools.toolbar.visible", false);
 
 // Pref to drive the devtools onboarding flow experiment. States:
 // - off: forces devtools.enabled to true
 // - on: devtools.enabled is not forced to true.
@@ -33,9 +33,9 @@ pref("devtools.onboarding.experiment", "
 // flipped once.
 pref("devtools.onboarding.experiment.flipped", false);
 
 // Flag to check if we already logged the devtools onboarding related probe.
 pref("devtools.onboarding.telemetry.logged", false);
 
 // Completely disable DevTools entry points, as well as all DevTools command line
 // arguments This should be merged with devtools.enabled, see Bug 1440675.
-pref("devtools.policy.disabled", false);
\ No newline at end of file
+pref("devtools.policy.disabled", false);
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -71,28 +71,26 @@ typedef BOOL (WINAPI *User32TrackPopupMe
                                             int nReserved,
                                             HWND hWnd,
                                             CONST RECT *prcRect);
 static WindowsDllInterceptor sUser32Intercept;
 static HWND sWinlessPopupSurrogateHWND = nullptr;
 static User32TrackPopupMenu sUser32TrackPopupMenuStub = nullptr;
 
 typedef HIMC (WINAPI *Imm32ImmGetContext)(HWND hWND);
-typedef BOOL (WINAPI *Imm32ImmReleaseContext)(HWND hWND, HIMC hIMC);
 typedef LONG (WINAPI *Imm32ImmGetCompositionString)(HIMC hIMC,
                                                     DWORD dwIndex,
                                                     LPVOID lpBuf,
                                                     DWORD dwBufLen);
 typedef BOOL (WINAPI *Imm32ImmSetCandidateWindow)(HIMC hIMC,
                                                   LPCANDIDATEFORM lpCandidate);
 typedef BOOL (WINAPI *Imm32ImmNotifyIME)(HIMC hIMC, DWORD dwAction,
                                         DWORD dwIndex, DWORD dwValue);
 static WindowsDllInterceptor sImm32Intercept;
 static Imm32ImmGetContext sImm32ImmGetContextStub = nullptr;
-static Imm32ImmReleaseContext sImm32ImmReleaseContextStub = nullptr;
 static Imm32ImmGetCompositionString sImm32ImmGetCompositionStringStub = nullptr;
 static Imm32ImmSetCandidateWindow sImm32ImmSetCandidateWindowStub = nullptr;
 static Imm32ImmNotifyIME sImm32ImmNotifyIME = nullptr;
 static PluginInstanceChild* sCurrentPluginInstance = nullptr;
 static const HIMC sHookIMC = (const HIMC)0xefefefef;
 
 using mozilla::gfx::SharedDIB;
 
@@ -2031,27 +2029,16 @@ PluginInstanceChild::ImmGetContextProc(H
         NS_WARNING("We cannot recongnize hooked window class");
         return sImm32ImmGetContextStub(aWND);
     }
 
     return sHookIMC;
 }
 
 // static
-BOOL
-PluginInstanceChild::ImmReleaseContextProc(HWND aWND, HIMC aIMC)
-{
-    if (aIMC == sHookIMC) {
-        return TRUE;
-    }
-
-    return sImm32ImmReleaseContextStub(aWND, aIMC);
-}
-
-// static
 LONG
 PluginInstanceChild::ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex,
                                                  LPVOID aBuf, DWORD aLen)
 {
     if (aIMC != sHookIMC) {
         return sImm32ImmGetCompositionStringStub(aIMC, aIndex, aBuf, aLen);
     }
     if (!sCurrentPluginInstance) {
@@ -2125,27 +2112,26 @@ PluginInstanceChild::InitImm32Hook()
         return;
     }
 
     if (sImm32ImmGetContextStub) {
         return;
     }
 
     // When using windowless plugin, IMM API won't work due ot OOP.
+    //
+    // ImmReleaseContext on Windows 7+ just returns TRUE only, so we don't
+    // need to hook this.
 
     sImm32Intercept.Init("imm32.dll");
     sImm32Intercept.AddHook(
         "ImmGetContext",
         reinterpret_cast<intptr_t>(ImmGetContextProc),
         (void**)&sImm32ImmGetContextStub);
     sImm32Intercept.AddHook(
-        "ImmReleaseContext",
-        reinterpret_cast<intptr_t>(ImmReleaseContextProc),
-        (void**)&sImm32ImmReleaseContextStub);
-    sImm32Intercept.AddHook(
         "ImmGetCompositionStringW",
         reinterpret_cast<intptr_t>(ImmGetCompositionStringProc),
         (void**)&sImm32ImmGetCompositionStringStub);
     sImm32Intercept.AddHook(
         "ImmSetCandidateWindow",
         reinterpret_cast<intptr_t>(ImmSetCandidateWindowProc),
         (void**)&sImm32ImmSetCandidateWindowStub);
     sImm32Intercept.AddHook(
--- a/dom/plugins/ipc/PluginInstanceChild.h
+++ b/dom/plugins/ipc/PluginInstanceChild.h
@@ -330,17 +330,16 @@ private:
                                           int nIndex,
                                           LONG newLong);
     static LONG WINAPI SetWindowLongWHook(HWND hWnd,
                                           int nIndex,
                                           LONG newLong);
 #endif
 
     static HIMC WINAPI ImmGetContextProc(HWND aWND);
-    static BOOL WINAPI ImmReleaseContextProc(HWND aWND, HIMC aIMC);
     static LONG WINAPI ImmGetCompositionStringProc(HIMC aIMC, DWORD aIndex,
                                                    LPVOID aBuf, DWORD aLen);
     static BOOL WINAPI ImmSetCandidateWindowProc(HIMC hIMC,
                                                  LPCANDIDATEFORM plCandidate);
     static BOOL WINAPI ImmNotifyIME(HIMC aIMC, DWORD aAction, DWORD aIndex,
                                     DWORD aValue);
 
     class FlashThrottleMsg : public CancelableRunnable
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -131,22 +131,35 @@ using mozilla::Nothing;
 using mozilla::NumberEqualsInt32;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::TimeDuration;
 using mozilla::TimeStamp;
 
 // Avoid an unnecessary NSPR dependency on Linux and OS X just for the shell.
 #ifdef JS_POSIX_NSPR
+
+enum PRLibSpecType { PR_LibSpec_Pathname };
+
+struct PRLibSpec {
+    PRLibSpecType type;
+    union {
+        const char *pathname;
+    } value;
+};
+
 typedef void PRLibrary;
 
-static PRLibrary*
-PR_LoadLibrary(const char* path)
-{
-    return dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
+#define PR_LD_NOW    RTLD_NOW
+#define PR_LD_GLOBAL RTLD_GLOBAL
+
+static PRLibrary *
+PR_LoadLibraryWithFlags(PRLibSpec libSpec, int flags)
+{
+    return dlopen(libSpec.value.pathname, flags);
 }
 
 static void
 PR_UnloadLibrary(PRLibrary* dll)
 {
     dlclose(dll);
 }
 #endif
--- 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)
@@ -815,18 +821,16 @@ private:
 
 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, PrefEntry::MatchEntry,
   PLDHashTable::MoveEntryStub, PrefEntry::ClearEntry,
   PrefEntry::InitEntry,
@@ -941,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;
   }
 
@@ -963,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",
@@ -1073,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.
@@ -1125,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)) {
@@ -1161,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);
@@ -2446,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
@@ -3154,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);
@@ -3410,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).
@@ -3439,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
@@ -3581,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 */)
@@ -3698,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;
@@ -3731,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.
@@ -3811,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;
@@ -3940,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);
@@ -4098,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)
 {
@@ -4124,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);
@@ -4148,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);
@@ -4180,17 +4216,16 @@ Preferences::LockInAnyProcess(const char
 
   Pref* pref = pref_HashTableLookup(aPrefName);
   if (!pref) {
     return NS_ERROR_UNEXPECTED;
   }
 
   if (!pref->IsLocked()) {
     pref->SetIsLocked(true);
-    gIsAnyPrefLocked = true;
     NotifyCallbacks(aPrefName);
   }
 
   return NS_OK;
 }
 
 /* static */ nsresult
 Preferences::Lock(const char* aPrefName)
@@ -4218,24 +4253,18 @@ Preferences::Unlock(const char* aPrefNam
   return NS_OK;
 }
 
 /* static */ bool
 Preferences::IsLocked(const char* aPrefName)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), false);
 
-  if (gIsAnyPrefLocked) {
-    Pref* pref = pref_HashTableLookup(aPrefName);
-    if (pref && pref->IsLocked()) {
-      return true;
-    }
-  }
-
-  return false;
+  Pref* pref = pref_HashTableLookup(aPrefName);
+  return pref && pref->IsLocked();
 }
 
 /* static */ nsresult
 Preferences::ClearUserInAnyProcess(const char* aPrefName)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
 
   PrefEntry* entry = pref_HashTableLookupInner(aPrefName);
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1084,33 +1084,33 @@ pref("toolkit.asyncshutdown.log", false)
 // Until bug 1361080 lands, we always consider them enabled.
 pref("devtools.enabled", true);
 
 // Enable deprecation warnings.
 pref("devtools.errorconsole.deprecation_warnings", true);
 
 #ifdef NIGHTLY_BUILD
 // Don't show the Browser Toolbox prompt on local builds / nightly
-sticky_pref("devtools.debugger.prompt-connection", false);
+pref("devtools.debugger.prompt-connection", false, sticky);
 #else
-sticky_pref("devtools.debugger.prompt-connection", true);
+pref("devtools.debugger.prompt-connection", true, sticky);
 #endif
 
 #ifdef MOZILLA_OFFICIAL
 // Disable debugging chrome
-sticky_pref("devtools.chrome.enabled", false);
+pref("devtools.chrome.enabled", false, sticky);
 // Disable remote debugging connections
-sticky_pref("devtools.debugger.remote-enabled", false);
+pref("devtools.debugger.remote-enabled", false, sticky);
 // enable JS dump() function.
-sticky_pref("browser.dom.window.dump.enabled", false);
+pref("browser.dom.window.dump.enabled", false, sticky);
 #else
 // In local builds, enable the browser toolbox by default
-sticky_pref("devtools.chrome.enabled", true);
-sticky_pref("devtools.debugger.remote-enabled", true);
-sticky_pref("browser.dom.window.dump.enabled", true);
+pref("devtools.chrome.enabled", true, sticky);
+pref("devtools.debugger.remote-enabled", true, sticky);
+pref("browser.dom.window.dump.enabled", true, sticky);
 #endif
 
 
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 pref("devtools.debugger.log.verbose", false);
 
 pref("devtools.debugger.remote-port", 6000);
--- 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);
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);
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);
--- a/modules/libpref/test/unit/data/testPrefSticky.js
+++ b/modules/libpref/test/unit/data/testPrefSticky.js
@@ -1,2 +1,2 @@
 pref("testPref.unsticky.bool", true);
-sticky_pref("testPref.sticky.bool", false);
+pref("testPref.sticky.bool", false, sticky);
--- a/modules/libpref/test/unit/data/testPrefStickyUser.js
+++ b/modules/libpref/test/unit/data/testPrefStickyUser.js
@@ -1,5 +1,5 @@
-// testPrefSticky.js defined this pref as a sticky_pref(). Once a sticky
+// testPrefSticky.js defined this pref as sticky. Once a sticky
 // pref has been changed, it's written as a user_pref().
 // So this test file reflects that scenario.
 // Note the default in testPrefSticky.js is also false.
 user_pref("testPref.sticky.bool", false);
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();
+});
--- 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,80 +39,80 @@ 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");
     Assert.ok(false, "expected failure reading this pref");
   } catch (ex) {
     Assert.ok(ex, "exception reading regular pref");
   }
   run_next_test();
 });
 
-// Loading a sticky_pref then a user_pref for the same pref means it should
+// 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/modules/libpref/test/unit/xpcshell.ini
+++ b/modules/libpref/test/unit/xpcshell.ini
@@ -6,15 +6,17 @@ 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_libPrefs.js]
 [test_bug1354613.js]
 [test_parser.js]
 support-files = data/testParser.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();
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -5094,22 +5094,24 @@
             ReadINIStr $R8 $R7 "Install" "MaintenanceService"
             ${If} $R8 == "false"
               StrCpy $InstallMaintenanceService "0"
             ${Else}
               ; Installing the service always requires elevation.
               ${ElevateUAC}
             ${EndIf}
 
-            ReadINIStr $R8 $R7 "Install" "OptionalExtensions"
-            ${If} $R8 == "false"
-              StrCpy $InstallOptionalExtensions "0"
-            ${Else}
-              StrCpy $InstallOptionalExtensions "1"
-            ${EndIf}
+            !ifdef MOZ_OPTIONAL_EXTENSIONS
+              ReadINIStr $R8 $R7 "Install" "OptionalExtensions"
+              ${If} $R8 == "false"
+                StrCpy $InstallOptionalExtensions "0"
+              ${Else}
+                StrCpy $InstallOptionalExtensions "1"
+              ${EndIf}
+            !endif
 
             !ifndef NO_STARTMENU_DIR
               ReadINIStr $R8 $R7 "Install" "StartMenuDirectoryName"
               ${If} $R8 != ""
                 StrCpy $StartMenuDir "$R8"
               ${EndIf}
             !endif
           ${EndIf}
--- a/toolkit/xre/test/win/TestDllInterceptor.cpp
+++ b/toolkit/xre/test/win/TestDllInterceptor.cpp
@@ -670,17 +670,16 @@ int main()
       TestHook(TestCreateDIBSection, "gdi32.dll", "CreateDIBSection") &&
       TestHook(TestCreateFileW, "kernel32.dll", "CreateFileW") &&    // see Bug 1316415
 #endif
       TestHook(TestCreateFileA, "kernel32.dll", "CreateFileA") &&
       TestHook(TestQueryDosDeviceW, "kernelbase.dll", "QueryDosDeviceW") &&
       TestDetour("user32.dll", "CreateWindowExW") &&
       TestHook(TestInSendMessageEx, "user32.dll", "InSendMessageEx") &&
       TestHook(TestImmGetContext, "imm32.dll", "ImmGetContext") &&
-      // TestHook("imm32.dll", "ImmReleaseContext") &&    // see Bug 1316415
       TestHook(TestImmGetCompositionStringW, "imm32.dll", "ImmGetCompositionStringW") &&
       TestHook(TestImmSetCandidateWindow, "imm32.dll", "ImmSetCandidateWindow") &&
       TestHook(TestImmNotifyIME, "imm32.dll", "ImmNotifyIME") &&
       TestHook(TestGetSaveFileNameW, "comdlg32.dll", "GetSaveFileNameW") &&
       TestHook(TestGetOpenFileNameW, "comdlg32.dll", "GetOpenFileNameW") &&
 #ifdef _M_X64
       TestHook(TestGetKeyState, "user32.dll", "GetKeyState") &&    // see Bug 1316415
       TestHook(TestLdrUnloadDll, "ntdll.dll", "LdrUnloadDll") &&
--- a/xpcom/ds/nsAtom.h
+++ b/xpcom/ds/nsAtom.h
@@ -33,41 +33,36 @@ public:
            memcmp(mString, aString, mLength * sizeof(char16_t)) == 0;
   }
 
   bool Equals(const nsAString& aString) const
   {
     return Equals(aString.BeginReading(), aString.Length());
   }
 
-  void SetKind(AtomKind aKind)
-  {
-    mKind = static_cast<uint32_t>(aKind);
-    MOZ_ASSERT(Kind() == aKind);
-  }
-
   AtomKind Kind() const { return static_cast<AtomKind>(mKind); }
 
   bool IsDynamicAtom() const { return Kind() == AtomKind::DynamicAtom; }
   bool IsHTML5Atom()   const { return Kind() == AtomKind::HTML5Atom; }
   bool IsStaticAtom()  const { return Kind() == AtomKind::StaticAtom; }
 
   char16ptr_t GetUTF16String() const { return mString; }
 
   uint32_t GetLength() const { return mLength; }
 
   void ToString(nsAString& aString) const;
   void ToUTF8String(nsACString& aString) const;
 
-  // This is only valid for dynamic atoms.
+  // This is not valid for static atoms. The caller must *not* mutate the
+  // string buffer, otherwise all hell will break loose.
   nsStringBuffer* GetStringBuffer() const
   {
     // See the comment on |mString|'s declaration.
-    MOZ_ASSERT(IsDynamicAtom());
-    return nsStringBuffer::FromData(mString);
+    MOZ_ASSERT(IsDynamicAtom() || IsHTML5Atom());
+    return nsStringBuffer::FromData(const_cast<char16_t*>(mString));
   }
 
   // A hashcode that is better distributed than the actual atom pointer, for
   // use in situations that need a well-distributed hashcode. It's called hash()
   // rather than Hash() so we can use mozilla::BloomFilter<N, nsAtom>, because
   // BloomFilter requires elements to implement a function called hash().
   //
   uint32_t hash() const
@@ -83,47 +78,47 @@ public:
 
   typedef mozilla::TrueType HasThreadSafeRefCnt;
 
 private:
   friend class nsAtomTable;
   friend class nsAtomSubTable;
   friend class nsHtml5AtomEntry;
 
-  // Dynamic atom construction is done by |friend|s.
+protected:
+  // Used by nsDynamicAtom and directly (by nsHtml5AtomEntry) for HTML5 atoms.
   nsAtom(AtomKind aKind, const nsAString& aString, uint32_t aHash);
 
-protected:
+  // Used by nsStaticAtom.
   nsAtom(const char16_t* aString, uint32_t aLength, uint32_t aHash);
 
   ~nsAtom();
 
-  mozilla::ThreadSafeAutoRefCnt mRefCnt;
-  uint32_t mLength: 30;
-  uint32_t mKind: 2; // nsAtom::AtomKind
-  uint32_t mHash;
+  const uint32_t mLength:30;
+  const uint32_t mKind:2; // nsAtom::AtomKind
+  const uint32_t mHash;
   // WARNING! For static atoms, this is a pointer to a static char buffer. For
   // non-static atoms it points to the chars in an nsStringBuffer. This means
   // that nsStringBuffer::FromData(mString) calls are only valid for non-static
   // atoms.
-  char16_t* mString;
+  const char16_t* const mString;
 };
 
 // A trivial subclass of nsAtom that can be used for known static atoms. The
 // main advantage of this class is that it doesn't require refcounting, so you
 // can use |nsStaticAtom*| in contrast with |RefPtr<nsAtom>|.
 //
 // This class would be |final| if it wasn't for nsICSSAnonBoxPseudo and
 // nsICSSPseudoElement, which are trivial subclasses used to ensure only
 // certain atoms are passed to certain functions.
 class nsStaticAtom : public nsAtom
 {
 public:
   // These are deleted so it's impossible to RefPtr<nsStaticAtom>. Raw
-  // nsStaticAtom atoms should be used instead.
+  // nsStaticAtom pointers should be used instead.
   MozExternalRefCountType AddRef() = delete;
   MozExternalRefCountType Release() = delete;
 
   already_AddRefed<nsAtom> ToAddRefed() {
     return already_AddRefed<nsAtom>(static_cast<nsAtom*>(this));
   }
 
 private:
--- a/xpcom/ds/nsAtomTable.cpp
+++ b/xpcom/ds/nsAtomTable.cpp
@@ -59,49 +59,85 @@ enum class GCKind {
 // (and thus turned into unused state), and decremented when an unused
 // atom gets a reference again. The atom table relies on this value to
 // schedule GC. This value can temporarily go below zero when multiple
 // threads are operating the same atom, so it has to be signed so that
 // we wouldn't use overflow value for comparison.
 // See nsAtom::AddRef() and nsAtom::Release().
 static Atomic<int32_t, ReleaseAcquire> gUnusedAtomCount(0);
 
-// This constructor is for dynamic atoms and HTML5 atoms.
-nsAtom::nsAtom(AtomKind aKind, const nsAString& aString, uint32_t aHash)
-  : mRefCnt(1)
-  , mLength(aString.Length())
-  , mKind(static_cast<uint32_t>(aKind))
-  , mHash(aHash)
+// Dynamic atoms need a ref count; this class adds that to nsAtom.
+class nsDynamicAtom : public nsAtom
 {
-  MOZ_ASSERT(aKind == AtomKind::DynamicAtom || aKind == AtomKind::HTML5Atom);
+public:
+  // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting
+  // of this type is special.
+  MozExternalRefCountType AddRef();
+  MozExternalRefCountType Release();
+
+  static nsDynamicAtom* As(nsAtom* aAtom)
+  {
+    MOZ_ASSERT(aAtom->IsDynamicAtom());
+    return static_cast<nsDynamicAtom*>(aAtom);
+  }
+
+private:
+  friend class nsAtomTable;
+  friend class nsAtomSubTable;
+
+  // Construction is done by |friend|s.
+  nsDynamicAtom(const nsAString& aString, uint32_t aHash)
+    : nsAtom(AtomKind::DynamicAtom, aString, aHash)
+    , mRefCnt(1)
+  {}
+
+  mozilla::ThreadSafeAutoRefCnt mRefCnt;
+};
+
+static char16_t*
+FromStringBuffer(const nsAString& aString)
+{
+  char16_t* str;
+  size_t length = aString.Length();
   RefPtr<nsStringBuffer> buf = nsStringBuffer::FromString(aString);
   if (buf) {
-    mString = static_cast<char16_t*>(buf->Data());
+    str = static_cast<char16_t*>(buf->Data());
   } else {
-    const size_t size = (mLength + 1) * sizeof(char16_t);
+    const size_t size = (length + 1) * sizeof(char16_t);
     buf = nsStringBuffer::Alloc(size);
     if (MOZ_UNLIKELY(!buf)) {
-      // We OOM because atom allocations should be small and it's hard to
-      // handle them more gracefully in a constructor.
-      NS_ABORT_OOM(size);
+      NS_ABORT_OOM(size); // OOM because atom allocations should be small.
     }
-    mString = static_cast<char16_t*>(buf->Data());
-    CopyUnicodeTo(aString, 0, mString, mLength);
-    mString[mLength] = char16_t(0);
+    str = static_cast<char16_t*>(buf->Data());
+    CopyUnicodeTo(aString, 0, str, length);
+    str[length] = char16_t(0);
   }
 
-  MOZ_ASSERT_IF(IsDynamicAtom(), mHash == HashString(mString, mLength));
+  MOZ_ASSERT(buf && buf->StorageSize() >= (length + 1) * sizeof(char16_t),
+             "enough storage");
+
+  // Take ownership of the string buffer.
+  mozilla::Unused << buf.forget();
+
+  return str;
+}
+
+// This constructor is for dynamic atoms and HTML5 atoms.
+nsAtom::nsAtom(AtomKind aKind, const nsAString& aString, uint32_t aHash)
+  : mLength(aString.Length())
+  , mKind(static_cast<uint32_t>(aKind))
+  , mHash(aHash)
+  , mString(FromStringBuffer(aString))
+{
+  MOZ_ASSERT(aKind == AtomKind::DynamicAtom || aKind == AtomKind::HTML5Atom);
+
+  MOZ_ASSERT_IF(!IsHTML5Atom(), mHash == HashString(mString, mLength));
 
   MOZ_ASSERT(mString[mLength] == char16_t(0), "null terminated");
-  MOZ_ASSERT(buf && buf->StorageSize() >= (mLength + 1) * sizeof(char16_t),
-             "enough storage");
   MOZ_ASSERT(Equals(aString), "correct data");
-
-  // Take ownership of buffer
-  mozilla::Unused << buf.forget();
 }
 
 // This constructor is for static atoms.
 nsAtom::nsAtom(const char16_t* aString, uint32_t aLength, uint32_t aHash)
   : mLength(aLength)
   , mKind(static_cast<uint32_t>(AtomKind::StaticAtom))
   , mHash(aHash)
   , mString(const_cast<char16_t*>(aString))
@@ -111,31 +147,31 @@ nsAtom::nsAtom(const char16_t* aString, 
   MOZ_ASSERT(mString[mLength] == char16_t(0), "null terminated");
   MOZ_ASSERT(NS_strlen(mString) == mLength, "correct storage");
 }
 
 nsAtom::~nsAtom()
 {
   if (!IsStaticAtom()) {
     MOZ_ASSERT(IsDynamicAtom() || IsHTML5Atom());
-    nsStringBuffer::FromData(mString)->Release();
+    GetStringBuffer()->Release();
   }
 }
 
 void
 nsAtom::ToString(nsAString& aString) const
 {
   // See the comment on |mString|'s declaration.
   if (IsStaticAtom()) {
     // AssignLiteral() lets us assign without copying. This isn't a string
     // literal, but it's a static atom and thus has an unbounded lifetime,
     // which is what's important.
     aString.AssignLiteral(mString, mLength);
   } else {
-    nsStringBuffer::FromData(mString)->ToString(mLength, aString);
+    GetStringBuffer()->ToString(mLength, aString);
   }
 }
 
 void
 nsAtom::ToUTF8String(nsACString& aBuf) const
 {
   MOZ_ASSERT(!IsHTML5Atom(), "Called ToUTF8String() on an HTML5 atom");
   CopyUTF16toUTF8(nsDependentString(mString, mLength), aBuf);
@@ -150,18 +186,17 @@ nsAtom::AddSizeOfIncludingThis(MallocSiz
   size_t thisSize = aMallocSizeOf(this);
   if (IsStaticAtom()) {
     // String buffers pointed to by static atoms are in static memory, and so
     // are not measured here.
     aSizes.mStaticAtomObjects += thisSize;
   } else {
     aSizes.mDynamicAtomObjects += thisSize;
     aSizes.mDynamicUnsharedBuffers +=
-      nsStringBuffer::FromData(mString)->SizeOfIncludingThisIfUnshared(
-        aMallocSizeOf);
+      GetStringBuffer()->SizeOfIncludingThisIfUnshared(aMallocSizeOf);
   }
 }
 
 //----------------------------------------------------------------------
 
 struct AtomTableKey
 {
   AtomTableKey(const char16_t* aUTF16String, uint32_t aLength,
@@ -469,17 +504,18 @@ nsAtomSubTable::GCLocked(GCKind aKind)
   uint32_t nonZeroRefcountAtomsCount = 0;
   for (auto i = mTable.Iter(); !i.Done(); i.Next()) {
     auto entry = static_cast<AtomTableEntry*>(i.Get());
     if (entry->mAtom->IsStaticAtom()) {
       continue;
     }
 
     nsAtom* atom = entry->mAtom;
-    if (atom->mRefCnt == 0) {
+    MOZ_ASSERT(!atom->IsHTML5Atom());
+    if (atom->IsDynamicAtom() && nsDynamicAtom::As(atom)->mRefCnt == 0) {
       i.Remove();
       delete atom;
       ++removedCount;
     }
 #ifdef NS_FREE_PERMANENT_DATA
     else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) {
       // Only report leaking atoms in leak-checking builds in a run where we
       // are checking for leaks, during shutdown. If something is anomalous,
@@ -511,42 +547,30 @@ static void
 GCAtomTable()
 {
   MOZ_ASSERT(gAtomTable);
   if (NS_IsMainThread()) {
     gAtomTable->GC(GCKind::RegularOperation);
   }
 }
 
-MozExternalRefCountType
-nsAtom::AddRef()
+MOZ_ALWAYS_INLINE MozExternalRefCountType
+nsDynamicAtom::AddRef()
 {
-  MOZ_ASSERT(!IsHTML5Atom(), "Attempt to AddRef an HTML5 atom");
-  if (!IsDynamicAtom()) {
-    MOZ_ASSERT(IsStaticAtom());
-    return 2;
-  }
-
   MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
   nsrefcnt count = ++mRefCnt;
   if (count == 1) {
     gUnusedAtomCount--;
   }
   return count;
 }
 
-MozExternalRefCountType
-nsAtom::Release()
+MOZ_ALWAYS_INLINE MozExternalRefCountType
+nsDynamicAtom::Release()
 {
-  MOZ_ASSERT(!IsHTML5Atom(), "Attempt to Release an HTML5 atom");
-  if (!IsDynamicAtom()) {
-    MOZ_ASSERT(IsStaticAtom());
-    return 1;
-  }
-
   #ifdef DEBUG
   // We set a lower GC threshold for atoms in debug builds so that we exercise
   // the GC machinery more often.
   static const int32_t kAtomGCThreshold = 20;
   #else
   static const int32_t kAtomGCThreshold = 10000;
   #endif
 
@@ -556,16 +580,32 @@ nsAtom::Release()
     if (++gUnusedAtomCount >= kAtomGCThreshold) {
       GCAtomTable();
     }
   }
 
   return count;
 }
 
+MozExternalRefCountType
+nsAtom::AddRef()
+{
+  MOZ_ASSERT(!IsHTML5Atom(), "Attempt to AddRef an HTML5 atom");
+
+  return IsStaticAtom() ? 2 : nsDynamicAtom::As(this)->AddRef();
+}
+
+MozExternalRefCountType
+nsAtom::Release()
+{
+  MOZ_ASSERT(!IsHTML5Atom(), "Attempt to Release an HTML5 atom");
+
+  return IsStaticAtom() ? 1 : nsDynamicAtom::As(this)->Release();
+}
+
 //----------------------------------------------------------------------
 
 // Have the static atoms been inserted into the table?
 static bool gStaticAtomsDone = false;
 
 class DefaultAtoms
 {
 public:
@@ -701,18 +741,17 @@ nsAtomTable::Atomize(const nsACString& a
     return atom.forget();
   }
 
   // This results in an extra addref/release of the nsStringBuffer.
   // Unfortunately there doesn't seem to be any APIs to avoid that.
   // Actually, now there is, sort of: ForgetSharedBuffer.
   nsString str;
   CopyUTF8toUTF16(aUTF8String, str);
-  RefPtr<nsAtom> atom =
-    dont_AddRef(new nsAtom(nsAtom::AtomKind::DynamicAtom, str, hash));
+  RefPtr<nsAtom> atom = dont_AddRef(new nsDynamicAtom(str, hash));
 
   he->mAtom = atom;
 
   return atom.forget();
 }
 
 already_AddRefed<nsAtom>
 NS_Atomize(const nsACString& aUTF8String)
@@ -738,18 +777,17 @@ nsAtomTable::Atomize(const nsAString& aU
   AtomTableEntry* he = table.Add(key);
 
   if (he->mAtom) {
     RefPtr<nsAtom> atom = he->mAtom;
 
     return atom.forget();
   }
 
-  RefPtr<nsAtom> atom =
-    dont_AddRef(new nsAtom(nsAtom::AtomKind::DynamicAtom, aUTF16String, hash));
+  RefPtr<nsAtom> atom = dont_AddRef(new nsDynamicAtom(aUTF16String, hash));
   he->mAtom = atom;
 
   return atom.forget();
 }
 
 already_AddRefed<nsAtom>
 NS_Atomize(const nsAString& aUTF16String)
 {
@@ -778,18 +816,17 @@ nsAtomTable::AtomizeMainThread(const nsA
 
   nsAtomSubTable& table = SelectSubTable(key);
   MutexAutoLock lock(table.mLock);
   AtomTableEntry* he = table.Add(key);
 
   if (he->mAtom) {
     retVal = he->mAtom;
   } else {
-    RefPtr<nsAtom> newAtom = dont_AddRef(
-      new nsAtom(nsAtom::AtomKind::DynamicAtom, aUTF16String, hash));
+    RefPtr<nsAtom> newAtom = dont_AddRef(new nsDynamicAtom(aUTF16String, hash));
     he->mAtom = newAtom;
     retVal = newAtom.forget();
   }
 
   sRecentlyUsedMainThreadAtoms[index] = he->mAtom;
   return retVal.forget();
 }