Bug 789945 - do async omt pref writes, r?bsmedberg draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 01 Jan 2016 12:39:02 +0000
changeset 322135 ac2de7237fcbb2cd283ac52491d4079c46b941b0
parent 322049 8701ea94d75174648832f4e932934f99595cefb5
child 322136 8386d7ac2802ba3105b098fcb97911ce27067205
push id9532
push usergijskruitbosch@gmail.com
push dateFri, 15 Jan 2016 20:02:20 +0000
reviewersbsmedberg
bugs789945
milestone46.0a1
Bug 789945 - do async omt pref writes, r?bsmedberg
modules/libpref/Preferences.cpp
modules/libpref/Preferences.h
modules/libpref/prefapi.cpp
modules/libpref/prefapi.h
modules/libpref/prefapi_private_data.h
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -23,16 +23,17 @@
 #include "nsNetUtil.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsIObserverService.h"
 #include "nsIOutputStream.h"
 #include "nsISafeOutputStream.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIStringEnumerator.h"
+#include "nsITimer.h"
 #include "nsIZipReader.h"
 #include "nsPrefBranch.h"
 #include "nsXPIDLString.h"
 #include "nsCRT.h"
 #include "nsCOMArray.h"
 #include "nsXPCOMCID.h"
 #include "nsAutoPtr.h"
 #include "nsPrintfCString.h"
@@ -80,16 +81,38 @@ static nsresult openPrefFile(nsIFile* aF
 static nsresult pref_InitInitialObjects(void);
 static nsresult pref_LoadPrefsInDirList(const char *listId);
 static nsresult ReadExtensionPrefs(nsIFile *aFile);
 
 static const char kTelemetryPref[] = "toolkit.telemetry.enabled";
 static const char kOldTelemetryPref[] = "toolkit.telemetry.enabledPreRelease";
 static const char kChannelPref[] = "app.update.channel";
 
+static const char kPrefFileHeader[] =
+  "# Mozilla User Preferences"
+  NS_LINEBREAK
+  NS_LINEBREAK
+  "/* Do not edit this file."
+  NS_LINEBREAK
+  " *"
+  NS_LINEBREAK
+  " * If you make changes to this file while the application is running,"
+  NS_LINEBREAK
+  " * the changes will be overwritten when the application exits."
+  NS_LINEBREAK
+  " *"
+  NS_LINEBREAK
+  " * To make a manual change to preferences, you can visit the URL about:config"
+  NS_LINEBREAK
+  " */"
+  NS_LINEBREAK
+  NS_LINEBREAK;
+
+static const uint32_t kPrefDelayedSaveTimeoutMs = 15000;
+
 Preferences* Preferences::sPreferences = nullptr;
 nsIPrefBranch* Preferences::sRootBranch = nullptr;
 nsIPrefBranch* Preferences::sDefaultRootBranch = nullptr;
 bool Preferences::sShutdown = false;
 
 class ValueObserverHashKey : public PLDHashEntryHdr {
 public:
   typedef ValueObserverHashKey* KeyType;
@@ -170,16 +193,127 @@ ValueObserver::Observe(nsISupports     *
   NS_ConvertUTF16toUTF8 data(aData);
   for (uint32_t i = 0; i < mClosures.Length(); i++) {
     mCallback(data.get(), mClosures.ElementAt(i));
   }
 
   return NS_OK;
 }
 
+class PreferenceRunnableSaveDone final : public nsRunnable
+{
+public:
+  PreferenceRunnableSaveDone() {}
+  NS_IMETHOD Run() {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (Preferences::sPreferences) {
+      Preferences::sPreferences->mCurrentOMTSaver = nullptr;
+      Preferences::sPreferences->mCurrentOMTSaveTimer = nullptr;
+      if (gDirty) {
+        Preferences::sPreferences->SavePrefsToDefaultFileOMT();
+      }
+    }
+    return NS_OK;
+  }
+};
+
+class PreferenceRunnableSaver final : public nsRunnable
+{
+public:
+  PreferenceRunnableSaver(nsIFile* aFile)
+    : mFile(aFile)
+    , mPrefs(pref_savePrefs(gHashTable, &mPrefCount))
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    nsCOMPtr<nsIOutputStream> outStreamSink;
+    nsCOMPtr<nsIOutputStream> outStream;
+    uint32_t                  writeAmount;
+    nsresult                  rv;
+    // execute a "safe" save by saving through a tempfile
+    rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), mFile,
+                                         -1, 0600);
+    if (NS_FAILED(rv)) {
+      // We set gDirty to false earlier, but we're not writing, so re-set it:
+      //XXXgijs this is racy, because I'm potentially setting it from
+      // non-mainthread. OTOH, there shouldn't be re-entrancy here
+      // because there's already a monitor and stuff. We only set to
+      // false when we re-start async writes (can't happen until
+      // after NotifyMainThreadIfNecessary() has finished on the main
+      // thread) and when we finish sync writes (can't happen until
+      // NotifyMainThreadIfNecessary() calls Notify() on the monitor).
+      gDirty = true;
+      NotifyMainThreadIfNecessary();
+      return rv;
+    }
+    rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink,
+                                    4096);
+    if (NS_FAILED(rv)) {
+      gDirty = true;
+      NotifyMainThreadIfNecessary();
+      return rv;
+    }
+
+    /* Sort the preferences to make a readable file on disk */
+    NS_QuickSort(mPrefs.get(), mPrefCount, sizeof(char *), pref_CompareStrings,
+                 nullptr);
+
+    // write out the file header
+    outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1,
+                     &writeAmount);
+
+    for (uint32_t valueIdx = 0; valueIdx < mPrefCount; valueIdx++) {
+      char*& pref = mPrefs[valueIdx];
+      if (pref) {
+        outStream->Write(pref, strlen(pref), &writeAmount);
+        outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
+        free(pref);
+        pref = nullptr;
+      }
+    }
+
+    // tell the safe output stream to overwrite the real prefs file
+    // (it'll abort if there were any errors during writing)
+    nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
+    NS_ASSERTION(safeStream, "expected a safe output stream!");
+    if (safeStream) {
+      rv = safeStream->Finish();
+      if (NS_FAILED(rv)) {
+        NS_WARNING("failed to save prefs file! possible data loss");
+      }
+    }
+
+    NotifyMainThreadIfNecessary();
+    return rv;
+  }
+
+  void
+  NotifyMainThreadIfNecessary()
+  {
+    if (!NS_IsMainThread()) {
+      // Notify the main thread we're done
+      mozilla::MonitorAutoLock mon(Preferences::sPreferences->mOMTSaveMonitor);
+      Preferences::sPreferences->mAreWaitingForOMTSave = false;
+
+      mon.Notify();
+
+      // Now dispatch something to finish us off on the main thread,
+      // and if necessary start a new timer for a new save
+      NS_DispatchToMainThread(new PreferenceRunnableSaveDone());
+    }
+  }
+
+private:
+  nsCOMPtr<nsIFile> mFile;
+  UniquePtr<char*[]> mPrefs;
+  uint32_t mPrefCount;
+};
+
 struct CacheData {
   void* cacheLocation;
   union {
     bool defaultValueBool;
     int32_t defaultValueInt;
     uint32_t defaultValueUint;
     float defaultValueFloat;
   };
@@ -447,16 +581,18 @@ Preferences::Shutdown()
 
 //-----------------------------------------------------------------------------
 
 /*
  * Constructor/Destructor
  */
 
 Preferences::Preferences()
+  : mAreWaitingForOMTSave(false),
+    mOMTSaveMonitor("Preferences::OMTSaveMonitor")
 {
 }
 
 Preferences::~Preferences()
 {
   NS_ASSERTION(sPreferences == this, "Isn't this the singleton instance?");
 
   delete gObserverTable;
@@ -538,16 +674,18 @@ Preferences::Init()
   if (!observerService)
     return NS_ERROR_FAILURE;
 
   rv = observerService->AddObserver(this, "profile-before-change", true);
 
   observerService->AddObserver(this, "load-extension-defaults", true);
   observerService->AddObserver(this, "suspend_process_notification", true);
 
+  gDirtyCallback = &PrefsDirtyCallback;
+
   return(rv);
 }
 
 // static
 nsresult
 Preferences::ResetAndReadUserPrefs()
 {
   sPreferences->ResetUserPrefs();
@@ -848,16 +986,18 @@ Preferences::MakeBackupPrefFile(nsIFile 
   NS_ENSURE_SUCCESS(rv, rv);
   return rv;
 }
 
 nsresult
 Preferences::ReadAndOwnUserPrefFile(nsIFile *aFile)
 {
   NS_ENSURE_ARG(aFile);
+
+  bool hadCurrentFile = !!mCurrentFile;
   
   if (mCurrentFile == aFile)
     return NS_OK;
   mCurrentFile = aFile;
 
   nsresult rv = NS_OK;
   bool exists = false;
   mCurrentFile->Exists(&exists);
@@ -868,117 +1008,119 @@ Preferences::ReadAndOwnUserPrefFile(nsIF
       // from the error line to the end of the file will be lost (bug 361102).
       // TODO we should notify the user about it (bug 523725).
       MakeBackupPrefFile(mCurrentFile);
     }
   } else {
     rv = NS_ERROR_FILE_NOT_FOUND;
   }
 
+  // If we had a current file before, save now if we're dirty
+  if (gDirty && hadCurrentFile) {
+    SavePrefsToDefaultFileOMT();
+  }
+
   return rv;
 }
 
 nsresult
 Preferences::SavePrefFileInternal(nsIFile *aFile)
 {
   if (nullptr == aFile) {
     // the gDirty flag tells us if we should write to mCurrentFile
     // we only check this flag when the caller wants to write to the default
     if (!gDirty)
       return NS_OK;
-    
+
+    // Cancel a pending timeout if we're saving anyway.
+    if (mCurrentOMTSaveTimer) {
+      if (!mAreWaitingForOMTSave) {
+#ifndef B2G
+        MOZ_ASSERT(NS_IsMainThread());
+#endif
+        // The timer fires on the main thread, and we should be on the main
+        // thread, so this shouldn't race:
+        mCurrentOMTSaveTimer->Cancel();
+        mCurrentOMTSaveTimer = nullptr;
+      } else {
+        // The async save is running in another thread, so wait for the monitor:
+        mozilla::MonitorAutoLock mon(mOMTSaveMonitor);
+
+        if (mAreWaitingForOMTSave) {
+          mon.Wait();
+        }
+      }
+    }
+
     // It's possible that we never got a prefs file.
     nsresult rv = NS_OK;
     if (mCurrentFile)
       rv = WritePrefFile(mCurrentFile);
 
     return rv;
   } else {
     return WritePrefFile(aFile);
   }
 }
 
 nsresult
 Preferences::WritePrefFile(nsIFile* aFile)
 {
-  const char                outHeader[] =
-    "# Mozilla User Preferences"
-    NS_LINEBREAK
-    NS_LINEBREAK
-    "/* Do not edit this file."
-    NS_LINEBREAK
-    " *"
-    NS_LINEBREAK
-    " * If you make changes to this file while the application is running,"
-    NS_LINEBREAK
-    " * the changes will be overwritten when the application exits."
-    NS_LINEBREAK
-    " *"
-    NS_LINEBREAK
-    " * To make a manual change to preferences, you can visit the URL about:config"
-    NS_LINEBREAK
-    " */"
-    NS_LINEBREAK
-    NS_LINEBREAK;
-
-  nsCOMPtr<nsIOutputStream> outStreamSink;
-  nsCOMPtr<nsIOutputStream> outStream;
-  uint32_t                  writeAmount;
-  nsresult                  rv;
-
   if (!gHashTable)
     return NS_ERROR_NOT_INITIALIZED;
 
-  // execute a "safe" save by saving through a tempfile
-  rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink),
-                                       aFile,
-                                       -1,
-                                       0600);
-  if (NS_FAILED(rv)) 
-      return rv;
-  rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096);
-  if (NS_FAILED(rv)) 
-      return rv;  
-
-  // get the lines that we're supposed to be writing to the file
-  UniquePtr<char*[]> valueArray = pref_savePrefs(gHashTable);
-
-  /* Sort the preferences to make a readable file on disk */
-  NS_QuickSort(valueArray.get(), gHashTable->EntryCount(), sizeof(char *),
-               pref_CompareStrings, nullptr);
-
-  // write out the file header
-  outStream->Write(outHeader, sizeof(outHeader) - 1, &writeAmount);
-
-  for (uint32_t valueIdx = 0; valueIdx < gHashTable->EntryCount(); valueIdx++) {
-    char*& pref = valueArray[valueIdx];
-    if (pref) {
-      outStream->Write(pref, strlen(pref), &writeAmount);
-      outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount);
-      free(pref);
-      pref = nullptr;
-    }
-  }
-
-  // tell the safe output stream to overwrite the real prefs file
-  // (it'll abort if there were any errors during writing)
-  nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream);
-  NS_ASSERTION(safeStream, "expected a safe output stream!");
-  if (safeStream) {
-    rv = safeStream->Finish();
-    if (NS_FAILED(rv)) {
-      NS_WARNING("failed to save prefs file! possible data loss");
-      return rv;
-    }
-  }
+  PreferenceRunnableSaver* saver = new PreferenceRunnableSaver(aFile);
+  saver->Run();
 
   gDirty = false;
   return NS_OK;
 }
 
+nsresult
+Preferences::SavePrefsToDefaultFileOMT()
+{
+  // Don't do one if one is still in progress. We'll fire a new one off when
+  // the previous one finds a gDirty state again.
+  if (!mCurrentFile || mCurrentOMTSaveTimer || mCurrentOMTSaver) {
+    return NS_OK;
+  }
+  mCurrentOMTSaveTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+
+  nsTimerCallbackFunc callback = [](nsITimer* aTimer, void* aClosure) {
+    auto prefs = static_cast<Preferences*>(aClosure);
+    nsresult rv;
+    nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+    // We might have done a sync save and no longer be in a dirty state
+    if (NS_SUCCEEDED(rv) && gDirty) {
+      gDirty = false;
+      prefs->mAreWaitingForOMTSave = true;
+      prefs->mCurrentOMTSaver = new PreferenceRunnableSaver(prefs->mCurrentFile);
+      target->Dispatch(prefs->mCurrentOMTSaver, nsIEventTarget::DISPATCH_NORMAL);
+    } else {
+      prefs->mCurrentOMTSaver = nullptr;
+      prefs->mCurrentOMTSaveTimer = nullptr;
+    }
+  };
+
+  mCurrentOMTSaveTimer->InitWithNamedFuncCallback(callback, this,
+      kPrefDelayedSaveTimeoutMs, nsITimer::TYPE_ONE_SHOT, "DelayedPrefSaver");
+  return NS_OK;
+}
+
+
+// static
+void
+Preferences::PrefsDirtyCallback()
+{
+  if (sPreferences && gHashTable) {
+    sPreferences->SavePrefsToDefaultFileOMT();
+  }
+}
+
+
 static nsresult openPrefFile(nsIFile* aFile)
 {
   nsCOMPtr<nsIInputStream> inStr;
 
   nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile);
   if (NS_FAILED(rv)) 
     return rv;        
 
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -9,36 +9,41 @@
 #ifndef MOZILLA_INTERNAL_API
 #error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)."
 #endif
 
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIPrefBranchInternal.h"
 #include "nsIObserver.h"
+#include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsWeakReference.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Monitor.h"
 
 class nsIFile;
 class nsAdoptingString;
 class nsAdoptingCString;
 
 #ifndef have_PrefChangedFunc_typedef
 typedef void (*PrefChangedFunc)(const char *, void *);
 #define have_PrefChangedFunc_typedef
 #endif
 
 namespace mozilla {
 
 namespace dom {
 class PrefSetting;
 } // namespace dom
 
+class PreferenceRunnableSaver;
+class PreferenceRunnableSaveDone;
+
 class Preferences final : public nsIPrefService,
                           public nsIObserver,
                           public nsIPrefBranchInternal,
                           public nsSupportsWeakReference
 {
 public:
   typedef mozilla::dom::PrefSetting PrefSetting;
 
@@ -344,16 +349,18 @@ public:
 
   // Used to synchronise preferences between chrome and content processes.
   static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs);
   static void GetPreference(PrefSetting* aPref);
   static void SetPreference(const PrefSetting& aPref);
 
   static int64_t SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf);
 
+  static void PrefsDirtyCallback();
+
 protected:
   virtual ~Preferences();
 
   nsresult NotifyServiceObservers(const char *aSubject);
   /**
    * Reads the default pref file or, if that failed, try to save a new one.
    *
    * @return NS_OK if either action succeeded,
@@ -361,26 +368,35 @@ protected:
    */
   nsresult UseDefaultPrefFile();
   nsresult UseUserPrefFile();
   nsresult ReadAndOwnUserPrefFile(nsIFile *aFile);
   nsresult ReadAndOwnSharedUserPrefFile(nsIFile *aFile);
   nsresult SavePrefFileInternal(nsIFile* aFile);
   nsresult WritePrefFile(nsIFile* aFile);
   nsresult MakeBackupPrefFile(nsIFile *aFile);
+  nsresult SavePrefsToDefaultFileOMT();
 
 private:
   nsCOMPtr<nsIFile>        mCurrentFile;
 
+  nsCOMPtr<nsITimer>       mCurrentOMTSaveTimer;
+  RefPtr<PreferenceRunnableSaver> mCurrentOMTSaver;
+  bool                     mAreWaitingForOMTSave;
+  mozilla::Monitor         mOMTSaveMonitor;
+
   static Preferences*      sPreferences;
   static nsIPrefBranch*    sRootBranch;
   static nsIPrefBranch*    sDefaultRootBranch;
   static bool              sShutdown;
 
   /**
    * Init static members.  TRUE if it succeeded.  Otherwise, FALSE.
    */
   static bool InitStaticMembers();
+
+  friend class PreferenceRunnableSaver;
+  friend class PreferenceRunnableSaveDone;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_Preferences_h
--- a/modules/libpref/prefapi.cpp
+++ b/modules/libpref/prefapi.cpp
@@ -67,16 +67,18 @@ matchPrefEntry(PLDHashTable*, const PLDH
     const char *otherKey = reinterpret_cast<const char*>(key);
     return (strcmp(prefEntry->key, otherKey) == 0);
 }
 
 PLDHashTable*       gHashTable;
 static PLArenaPool  gPrefNameArena;
 bool                gDirty = false;
 
+PrefsDirtyFunc gDirtyCallback = nullptr;
+
 static struct CallbackNode* gCallbacks = nullptr;
 static bool         gIsAnyPrefLocked = false;
 // These are only used during the call to pref_DoCallback
 static bool         gCallbacksInProgress = false;
 static bool         gShouldCleanupDeadNodes = false;
 
 
 static PLDHashTableOps     pref_HashTableOps = {
@@ -314,17 +316,17 @@ pref_SetPref(const dom::PrefSetting& aPr
 
     // NB: we should never try to clear a default value, that doesn't
     // make sense
 
     return rv;
 }
 
 UniquePtr<char*[]>
-pref_savePrefs(PLDHashTable* aTable)
+pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount)
 {
     auto savedPrefs = MakeUnique<char*[]>(aTable->EntryCount());
     memset(savedPrefs.get(), 0, aTable->EntryCount() * sizeof(char*));
 
     int32_t j = 0;
     for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
         auto pref = static_cast<PrefHashEntry*>(iter.Get());
 
@@ -364,16 +366,17 @@ pref_savePrefs(PLDHashTable* aTable)
         str_escape(pref->key, prefName);
 
         savedPrefs[j++] = ToNewCString(prefPrefix +
                                        prefName +
                                        NS_LITERAL_CSTRING("\", ") +
                                        prefValue +
                                        NS_LITERAL_CSTRING(");"));
     }
+    *aPrefCount = j;
 
     return savedPrefs;
 }
 
 static void
 GetPrefValueFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref,
                       WhichValue aWhich)
 {
@@ -567,17 +570,17 @@ PREF_DeleteBranch(const char *branch_nam
             and "ldap" (if such a leaf node exists) but not "ldap_1.xxx" */
         if (PL_strncmp(entry->key, to_delete, (uint32_t) len) == 0 ||
             (len-1 == (int)strlen(entry->key) &&
              PL_strncmp(entry->key, to_delete, (uint32_t)(len-1)) == 0)) {
             iter.Remove();
         }
     }
 
-    gDirty = true;
+    PREF_MarkDirty();
     return NS_OK;
 }
 
 
 nsresult
 PREF_ClearUserPref(const char *pref_name)
 {
     if (!gHashTable)
@@ -588,17 +591,17 @@ PREF_ClearUserPref(const char *pref_name
     {
         pref->flags &= ~PREF_USERSET;
 
         if (!(pref->flags & PREF_HAS_DEFAULT)) {
             gHashTable->RemoveEntry(pref);
         }
 
         pref_DoCallback(pref_name);
-        gDirty = true;
+        PREF_MarkDirty();
     }
     return NS_OK;
 }
 
 nsresult
 PREF_ClearAllUserPrefs()
 {
 #ifndef MOZ_B2G
@@ -621,17 +624,17 @@ PREF_ClearAllUserPrefs()
             }
         }
     }
 
     for (std::string& prefString : prefStrings) {
         pref_DoCallback(prefString.c_str());
     }
 
-    gDirty = true;
+    PREF_MarkDirty();
     return NS_OK;
 }
 
 nsresult PREF_LockPref(const char *key, bool lockit)
 {
     if (!gHashTable)
         return NS_ERROR_NOT_INITIALIZED;
 
@@ -763,29 +766,29 @@ nsresult pref_HashPref(const char *key, 
             !pref_ValueChanged(pref->defaultPref, value, type) &&
             !(flags & kPrefForceSet))
         {
             if (PREF_HAS_USER_VALUE(pref))
             {
                 /* XXX should we free a user-set string value if there is one? */
                 pref->flags &= ~PREF_USERSET;
                 if (!PREF_IS_LOCKED(pref)) {
-                    gDirty = true;
+                    PREF_MarkDirty();
                     valueChanged = true;
                 }
             }
         }
         else if (!PREF_HAS_USER_VALUE(pref) ||
                  PREF_TYPE(pref) != type ||
                  pref_ValueChanged(pref->userPref, value, type) )
         {
             pref_SetValue(&pref->userPref, &pref->flags, value, type);
             pref->flags |= PREF_USERSET;
             if (!PREF_IS_LOCKED(pref)) {
-                gDirty = true;
+                PREF_MarkDirty();
                 valueChanged = true;
             }
         }
     }
 
     if (valueChanged) {
         return pref_DoCallback(key);
     }
@@ -975,8 +978,19 @@ void PREF_ReaderCallback(void       *clo
                          bool        isStickyDefault)
 {
     uint32_t flags = isDefault ? kPrefSetDefault : kPrefForceSet;
     if (isDefault && isStickyDefault) {
         flags |= kPrefStickyDefault;
     }
     pref_HashPref(pref, value, type, flags);
 }
+
+void PREF_MarkDirty()
+{
+  if (!gDirty) {
+    gDirty = true;
+    if (gDirtyCallback) {
+      gDirtyCallback();
+    }
+  }
+}
+
--- a/modules/libpref/prefapi.h
+++ b/modules/libpref/prefapi.h
@@ -183,12 +183,19 @@ nsresult PREF_UnregisterCallback( const 
  */
 void PREF_ReaderCallback( void *closure,
                           const char *pref,
                           PrefValue   value,
                           PrefType    type,
                           bool        isDefault,
                           bool        isStickyDefault);
 
+/*
+ * Mark the prefs as dirty (triggers the dirty callback if the prefs were not
+ * previously dirty)
+ */
+void PREF_MarkDirty();
+
+typedef void (*PrefsDirtyFunc) ();
 #ifdef __cplusplus
 }
 #endif
 #endif
--- a/modules/libpref/prefapi_private_data.h
+++ b/modules/libpref/prefapi_private_data.h
@@ -8,25 +8,27 @@
 #ifndef prefapi_private_data_h
 #define prefapi_private_data_h
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/UniquePtr.h"
 
 extern PLDHashTable* gHashTable;
 extern bool gDirty;
+typedef void (*PrefsDirtyFunc) ();
+extern PrefsDirtyFunc gDirtyCallback;
 
 namespace mozilla {
 namespace dom {
 class PrefSetting;
 } // namespace dom
 } // namespace mozilla
 
 mozilla::UniquePtr<char*[]>
-pref_savePrefs(PLDHashTable* aTable);
+pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount);
 
 nsresult
 pref_SetPref(const mozilla::dom::PrefSetting& aPref);
 
 int pref_CompareStrings(const void *v1, const void *v2, void* unused);
 PrefHashEntry* pref_HashTableLookup(const char *key);
 
 void pref_GetPrefFromEntry(PrefHashEntry *aHashEntry,