Bug 723002 - Determine privacy status from provided nsILoadContext in ContentPrefService. r=ehsan
authorJosh Matthews <josh@joshmatthews.net>
Sat, 30 Jun 2012 07:50:07 -0700
changeset 111829 53b97b4ec554925108480bb34ecf5cdeb0659a62
parent 111828 ce8e092cb0790b2175bbbca04bf5f55bf64675b6
child 111830 63ce297be1f21ec26001fdcd20b9318fb29b6391
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersehsan
bugs723002
milestone19.0a1
Bug 723002 - Determine privacy status from provided nsILoadContext in ContentPrefService. r=ehsan
browser/base/content/browser-fullZoom.js
browser/base/content/sanitize.js
browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_DownloadLastDirWithCPS.js
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
dom/interfaces/base/nsIContentPrefService.idl
editor/composer/src/nsEditorSpellCheck.cpp
mobile/android/chrome/content/browser.js
mobile/android/chrome/content/sanitize.js
services/common/preferences.js
services/common/tests/unit/test_preferences.js
toolkit/components/contentprefs/ContentPrefInstance.jsm
toolkit/components/contentprefs/Makefile.in
toolkit/components/contentprefs/nsContentPrefService.js
toolkit/components/contentprefs/nsContentPrefService.manifest
toolkit/components/contentprefs/tests/unit/head_contentPrefs.js
toolkit/components/contentprefs/tests/unit/test_bug248970.js
toolkit/components/contentprefs/tests/unit/test_bug503971.js
toolkit/components/contentprefs/tests/unit/test_bug679784.js
toolkit/components/contentprefs/tests/unit/test_contentPrefs.js
toolkit/components/contentprefs/tests/unit/test_contentPrefsCache.js
toolkit/components/contentprefs/tests/unit/test_getPrefAsync.js
toolkit/components/contentprefs/tests/unit/test_stringGroups.js
toolkit/components/contentprefs/tests/unit/test_unusedGroupsAndSettings.js
toolkit/components/contentprefs/tests/unit_ipc/contentPrefs_childipc.js
toolkit/components/contentprefs/tests/unit_ipc/test_contentPrefs_parentipc.js
toolkit/forgetaboutsite/ForgetAboutSite.jsm
toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
toolkit/mozapps/downloads/DownloadLastDir.jsm
--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -5,28 +5,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 #endif
  */
 
 // One of the possible values for the mousewheel.* preferences.
 // From nsEventStateManager.cpp.
 const MOUSE_SCROLL_ZOOM = 3;
 
+Cu.import('resource://gre/modules/ContentPrefInstance.jsm');
+
+function getContentPrefs(aWindow) {
+  let context = aWindow ? aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsIWebNavigation)
+                                 .QueryInterface(Ci.nsILoadContext) : null;
+  return new ContentPrefInstance(context);
+}
+
 /**
  * Controls the "full zoom" setting and its site-specific preferences.
  */
 var FullZoom = {
   // Identifies the setting in the content prefs database.
   name: "browser.content.full-zoom",
 
   // The global value (if any) for the setting.  Lazily loaded from the service
   // when first requested, then updated by the pref change listener as it changes.
   // If there is no global value, then this should be undefined.
   get globalValue() {
-    var globalValue = Services.contentPrefs.getPref(null, this.name);
+    var globalValue = getContentPrefs(gBrowser.contentDocument.defaultView).getPref(null, this.name);
     if (typeof globalValue != "undefined")
       globalValue = this._ensureValid(globalValue);
     delete this.globalValue;
     return this.globalValue = globalValue;
   },
 
   // browser.zoom.siteSpecific preference cache
   _siteSpecificPref: undefined,
@@ -50,30 +59,30 @@ var FullZoom = {
   //**************************************************************************//
   // Initialization & Destruction
 
   init: function FullZoom_init() {
     // Listen for scrollwheel events so we can save scrollwheel-based changes.
     window.addEventListener("DOMMouseScroll", this, false);
 
     // Register ourselves with the service so we know when our pref changes.
-    Services.contentPrefs.addObserver(this.name, this);
+    getContentPrefs().addObserver(this.name, this);
 
     this._siteSpecificPref =
       gPrefService.getBoolPref("browser.zoom.siteSpecific");
     this.updateBackgroundTabs =
       gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
     // Listen for changes to the browser.zoom branch so we can enable/disable
     // updating background tabs and per-site saving and restoring of zoom levels.
     gPrefService.addObserver("browser.zoom.", this, true);
   },
 
   destroy: function FullZoom_destroy() {
     gPrefService.removeObserver("browser.zoom.", this);
-    Services.contentPrefs.removeObserver(this.name, this);
+    getContentPrefs().removeObserver(this.name, this);
     window.removeEventListener("DOMMouseScroll", this, false);
   },
 
 
   //**************************************************************************//
   // Event Handlers
 
   // nsIDOMEventListener
@@ -144,39 +153,41 @@ var FullZoom = {
         }
         break;
     }
   },
 
   // nsIContentPrefObserver
 
   onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) {
-    if (aGroup == Services.contentPrefs.grouper.group(gBrowser.currentURI))
+    let contentPrefs = getContentPrefs(gBrowser.contentDocument.defaultView);
+    if (aGroup == contentPrefs.grouper.group(gBrowser.currentURI))
       this._applyPrefToSetting(aValue);
     else if (aGroup == null) {
       this.globalValue = this._ensureValid(aValue);
 
       // If the current page doesn't have a site-specific preference,
       // then its zoom should be set to the new global preference now that
       // the global preference has changed.
-      if (!Services.contentPrefs.hasPref(gBrowser.currentURI, this.name))
+      if (!contentPrefs.hasPref(gBrowser.currentURI, this.name))
         this._applyPrefToSetting();
     }
   },
 
   onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) {
-    if (aGroup == Services.contentPrefs.grouper.group(gBrowser.currentURI))
+    let contentPrefs = getContentPrefs(gBrowser.contentDocument.defaultView);
+    if (aGroup == contentPrefs.grouper.group(gBrowser.currentURI))
       this._applyPrefToSetting();
     else if (aGroup == null) {
       this.globalValue = undefined;
 
       // If the current page doesn't have a site-specific preference,
       // then its zoom should be set to the default preference now that
       // the global preference has changed.
-      if (!Services.contentPrefs.hasPref(gBrowser.currentURI, this.name))
+      if (!contentPrefs.hasPref(gBrowser.currentURI, this.name))
         this._applyPrefToSetting();
     }
   },
 
   // location change observer
 
   /**
    * Called when the location of a tab changes.
@@ -202,22 +213,23 @@ var FullZoom = {
     let browser = aBrowser || gBrowser.selectedBrowser;
 
     // Media documents should always start at 1, and are not affected by prefs.
     if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) {
       ZoomManager.setZoomForBrowser(browser, 1);
       return;
     }
 
-    if (Services.contentPrefs.hasCachedPref(aURI, this.name)) {
-      let zoomValue = Services.contentPrefs.getPref(aURI, this.name);
+    let contentPrefs = getContentPrefs(gBrowser.contentDocument.defaultView);
+    if (contentPrefs.hasCachedPref(aURI, this.name)) {
+      let zoomValue = contentPrefs.getPref(aURI, this.name);
       this._applyPrefToSetting(zoomValue, browser);
     } else {
       var self = this;
-      Services.contentPrefs.getPref(aURI, this.name, function (aResult) {
+      contentPrefs.getPref(aURI, this.name, function (aResult) {
         // Check that we're still where we expect to be in case this took a while.
         // Null check currentURI, since the window may have been destroyed before
         // we were called.
         if (browser.currentURI && aURI.equals(browser.currentURI)) {
           self._applyPrefToSetting(aResult, browser);
         }
       });
     }
@@ -292,22 +304,22 @@ var FullZoom = {
   },
 
   _applySettingToPref: function FullZoom__applySettingToPref() {
     if (!this.siteSpecific || gInPrintPreviewMode ||
         content.document.mozSyntheticDocument)
       return;
 
     var zoomLevel = ZoomManager.zoom;
-    Services.contentPrefs.setPref(gBrowser.currentURI, this.name, zoomLevel);
+    getContentPrefs(gBrowser.contentDocument.defaultView).setPref(gBrowser.currentURI, this.name, zoomLevel);
   },
 
   _removePref: function FullZoom__removePref() {
     if (!(content.document.mozSyntheticDocument))
-      Services.contentPrefs.removePref(gBrowser.currentURI, this.name);
+      getContentPrefs(gBrowser.contentDocument.defaultView).removePref(gBrowser.currentURI, this.name);
   },
 
 
   //**************************************************************************//
   // Utilities
 
   _ensureValid: function FullZoom__ensureValid(aValue) {
     if (isNaN(aValue))
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -351,17 +351,17 @@ Sanitizer.prototype = {
         // Clear site-specific permissions like "Allow this site to open popups"
         var pm = Components.classes["@mozilla.org/permissionmanager;1"]
                            .getService(Components.interfaces.nsIPermissionManager);
         pm.removeAll();
         
         // Clear site-specific settings like page-zoom level
         var cps = Components.classes["@mozilla.org/content-pref/service;1"]
                             .getService(Components.interfaces.nsIContentPrefService);
-        cps.removeGroupedPrefs();
+        cps.removeGroupedPrefs(null);
         
         // Clear "Never remember passwords for this site", which is not handled by
         // the permission manager
         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
                               .getService(Components.interfaces.nsILoginManager);
         var hosts = pwmgr.getAllDisabledHosts();
         for each (var host in hosts) {
           pwmgr.setLoginSavingEnabled(host, true);
--- a/browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_DownloadLastDirWithCPS.js
+++ b/browser/components/privatebrowsing/test/browser/global/browser_privatebrowsing_DownloadLastDirWithCPS.js
@@ -109,17 +109,17 @@ function runTest() {
       is(gDownloadLastDir.getFile(uri2).path, dir2.path, "uri2 should return dir2"); // set in CPS
       is(gDownloadLastDir.getFile(uri3).path, dir3.path, "uri3 should return dir3"); // set in CPS
       is(gDownloadLastDir.getFile(uri4).path, dir2.path, "uri4 should return dir2"); // fallback
     }
 
     { // check clearHistory removes all data
       clearHistory();
       is(gDownloadLastDir.file, null, "clearHistory removes all data");
-      is(Services.contentPrefs.hasPref(uri1, "browser.download.lastDir"), false, "LastDir preference should be absent");
+      is(Services.contentPrefs.hasPref(uri1, "browser.download.lastDir", null), false, "LastDir preference should be absent");
       is(gDownloadLastDir.getFile(uri1), null, "uri1 should point to null");
       is(gDownloadLastDir.getFile(uri2), null, "uri2 should point to null");
       is(gDownloadLastDir.getFile(uri3), null, "uri3 should point to null");
       is(gDownloadLastDir.getFile(uri4), null, "uri4 should point to null");
     }
 
     Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session", true);
 
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -270,17 +270,17 @@ nsHTMLInputElement::nsFilePickerShownCal
         continue;
       }
       nsCOMPtr<nsIDOMFile> domFile =
         do_QueryObject(new nsDOMFileFile(localFile));
       newFiles.AppendObject(domFile);
       if (!prefSaved) {
         // Store the last used directory using the content pref service
         nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
-          mInput->OwnerDoc()->GetDocumentURI(), localFile);
+          mInput->OwnerDoc(), localFile);
         prefSaved = true;
       }
     }
   }
   else {
     nsCOMPtr<nsIFile> localFile;
     nsresult rv = mFilePicker->GetFile(getter_AddRefs(localFile));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -288,17 +288,17 @@ nsHTMLInputElement::nsFilePickerShownCal
       nsString path;
       rv = localFile->GetPath(path);
       if (!path.IsEmpty()) {
         nsCOMPtr<nsIDOMFile> domFile=
           do_QueryObject(new nsDOMFileFile(localFile));
         newFiles.AppendObject(domFile);
         // Store the last used directory using the content pref service
         nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
-          mInput->OwnerDoc()->GetDocumentURI(), localFile);
+          mInput->OwnerDoc(), localFile);
       }
     }
   }
 
   if (!newFiles.Count()) {
     return NS_OK;
   }
 
@@ -405,17 +405,17 @@ nsHTMLInputElement::AsyncClickHandler::R
       oldFiles[0]->GetName(leafName);
       if (!leafName.IsEmpty()) {
         filePicker->SetDefaultString(leafName);
       }
     }
   } else {
     // Attempt to retrieve the last used directory from the content pref service
     nsCOMPtr<nsIFile> localFile;
-    nsHTMLInputElement::gUploadLastDir->FetchLastUsedDirectory(doc->GetDocumentURI(),
+    nsHTMLInputElement::gUploadLastDir->FetchLastUsedDirectory(doc,
                                                                getter_AddRefs(localFile));
     if (!localFile) {
       // Default to "desktop" directory for each platform
       nsCOMPtr<nsIFile> homeDir;
       NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir));
       localFile = do_QueryInterface(homeDir);
     }
     filePicker->SetDisplayDirectory(localFile);
@@ -443,88 +443,102 @@ nsHTMLInputElement::InitUploadLastDir() 
 }
 
 void 
 nsHTMLInputElement::DestroyUploadLastDir() {
   NS_IF_RELEASE(gUploadLastDir);
 }
 
 nsresult
-UploadLastDir::FetchLastUsedDirectory(nsIURI* aURI, nsIFile** aFile)
+UploadLastDir::FetchLastUsedDirectory(nsIDocument* aDoc, nsIFile** aFile)
 {
-  NS_PRECONDITION(aURI, "aURI is null");
+  NS_PRECONDITION(aDoc, "aDoc is null");
   NS_PRECONDITION(aFile, "aFile is null");
+
+  nsIURI* docURI = aDoc->GetDocumentURI();
+  NS_PRECONDITION(docURI, "docURI is null");
+
+  nsCOMPtr<nsISupports> container = aDoc->GetContainer();
+  nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(container);
+
   // Attempt to get the CPS, if it's not present we'll just return
   nsCOMPtr<nsIContentPrefService> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   if (!contentPrefService)
     return NS_ERROR_NOT_AVAILABLE;
   nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
   if (!uri)
     return NS_ERROR_OUT_OF_MEMORY;
-  uri->SetAsISupports(aURI);
+  uri->SetAsISupports(docURI);
 
   // Get the last used directory, if it is stored
   bool hasPref;
-  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, &hasPref)) && hasPref) {
+  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, loadContext, &hasPref)) && hasPref) {
     nsCOMPtr<nsIVariant> pref;
-    contentPrefService->GetPref(uri, CPS_PREF_NAME, nullptr, getter_AddRefs(pref));
+    contentPrefService->GetPref(uri, CPS_PREF_NAME, loadContext, nullptr, getter_AddRefs(pref));
     nsString prefStr;
     pref->GetAsAString(prefStr);
 
     nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
     if (!localFile)
       return NS_ERROR_OUT_OF_MEMORY;
     localFile->InitWithPath(prefStr);
     localFile.forget(aFile);
   }
   return NS_OK;
 }
 
 nsresult
-UploadLastDir::StoreLastUsedDirectory(nsIURI* aURI, nsIFile* aFile)
+UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aFile)
 {
-  NS_PRECONDITION(aURI, "aURI is null");
+  NS_PRECONDITION(aDoc, "aDoc is null");
   NS_PRECONDITION(aFile, "aFile is null");
+
+  nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
+  NS_PRECONDITION(docURI, "docURI is null");
+
   nsCOMPtr<nsIFile> parentFile;
   aFile->GetParent(getter_AddRefs(parentFile));
   if (!parentFile) {
     return NS_OK;
   }
 
   // Attempt to get the CPS, if it's not present we'll just return
   nsCOMPtr<nsIContentPrefService> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   if (!contentPrefService)
     return NS_ERROR_NOT_AVAILABLE;
   nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
   if (!uri)
     return NS_ERROR_OUT_OF_MEMORY;
-  uri->SetAsISupports(aURI);
+  uri->SetAsISupports(docURI);
  
   // Find the parent of aFile, and store it
   nsString unicodePath;
   parentFile->GetPath(unicodePath);
   if (unicodePath.IsEmpty()) // nothing to do
     return NS_OK;
   nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
   if (!prefValue)
     return NS_ERROR_OUT_OF_MEMORY;
   prefValue->SetAsAString(unicodePath);
-  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue);
+
+  nsCOMPtr<nsISupports> container = aDoc->GetContainer();
+  nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(container);
+  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue, loadContext);
 }
 
 NS_IMETHODIMP
 UploadLastDir::Observe(nsISupports *aSubject, char const *aTopic, PRUnichar const *aData)
 {
   if (strcmp(aTopic, "browser:purge-session-history") == 0) {
     nsCOMPtr<nsIContentPrefService> contentPrefService =
       do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
     if (contentPrefService)
-      contentPrefService->RemovePrefsByName(CPS_PREF_NAME);
+      contentPrefService->RemovePrefsByName(CPS_PREF_NAME, nullptr);
   }
   return NS_OK;
 }
 
 #ifdef ACCESSIBILITY
 //Helper method
 static nsresult FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
                                           nsPresContext* aPresContext,
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -30,29 +30,29 @@ class UploadLastDir MOZ_FINAL : public n
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   /**
    * Fetch the last used directory for this location from the content
    * pref service, if it is available.
    *
-   * @param aURI URI of the current page
+   * @param aDoc  current document
    * @param aFile path to the last used directory
    */
-  nsresult FetchLastUsedDirectory(nsIURI* aURI, nsIFile** aFile);
+  nsresult FetchLastUsedDirectory(nsIDocument* aDoc, nsIFile** aFile);
 
   /**
    * Store the last used directory for this location using the
    * content pref service, if it is available
    * @param aURI URI of the current page
    * @param aFile file chosen by the user - the path to the parent of this
    *        file will be stored
    */
-  nsresult StoreLastUsedDirectory(nsIURI* aURI, nsIFile* aFile);
+  nsresult StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aFile);
 };
 
 class nsHTMLInputElement : public nsGenericHTMLFormElement,
                            public nsImageLoadingContent,
                            public nsIDOMHTMLInputElement,
                            public nsITextControlElement,
                            public nsIPhonetic,
                            public nsIDOMNSEditableElement,
--- a/dom/interfaces/base/nsIContentPrefService.idl
+++ b/dom/interfaces/base/nsIContentPrefService.idl
@@ -2,16 +2,17 @@
  * 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 "nsISupports.idl"
 
 interface nsIVariant;
 interface nsIPropertyBag2;
 interface nsIContentURIGrouper;
+interface nsILoadContext;
 interface mozIStorageConnection;
 
 [scriptable, uuid(746c7a02-f6c1-4869-b434-7c8b86e60e61)]
 interface nsIContentPrefObserver : nsISupports
 {
   /**
    * Called when a content pref is set to a different value.
    * 
@@ -33,17 +34,17 @@ interface nsIContentPrefObserver : nsISu
 };
 
 [scriptable, function, uuid(c1b3d6df-5373-4606-8494-8bcf14a7fc62)]
 interface nsIContentPrefCallback : nsISupports
 {
   void onResult(in nsIVariant aResult);
 };
 
-[scriptable, uuid(0014e2b4-1bab-4946-9211-7d28fc8df4d7)]
+[scriptable, uuid(e3f772f3-023f-4b32-b074-36cf0fd5d414)]
 interface nsIContentPrefService : nsISupports
 {
   /**
    * Get a pref.
    *
    * Besides the regular string, integer, boolean, etc. values, this method
    * may return null (nsIDataType::VTYPE_EMPTY), which means the pref is set
    * to NULL in the database, as well as undefined (nsIDataType::VTYPE_VOID),
@@ -52,119 +53,166 @@ interface nsIContentPrefService : nsISup
    * This method can be called from content processes in electrolysis builds.
    * We have a whitelist of values that can be read in such a way.
    *
    * @param    aGroup      the group for which to get the pref, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null 
    *                       to get the global pref (applies to all sites)
    * @param    aName       the name of the pref to get
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to search in memory or in
+   *                       permanent storage for it), obtained from a relevant
+   *                       window or channel.
    * @param    aCallback   an optional nsIContentPrefCallback to receive the
    *                       result. If desired, JavaScript callers can instead
    *                       provide a function to call upon completion
    *
    * @returns  the value of the pref
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
   nsIVariant getPref(in nsIVariant aGroup, in AString aName,
+                     in nsILoadContext aPrivacyContext,
                      [optional] in nsIContentPrefCallback aCallback);
 
   /**
    * Set a pref.
    *
    * This method can be called from content processes in electrolysis builds.
    * We have a whitelist of values that can be set in such a way.
    *
    * @param    aGroup      the group for which to set the pref, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to set the global pref (applies to all sites)
    * @param    aName       the name of the pref to set
    * @param    aValue      the new value of the pref
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to store it in memory or in
+   *                       permanent storage), obtained from a relevant
+   *                       window or channel.
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
-  void setPref(in nsIVariant aGroup, in AString aName, in nsIVariant aValue);
+  void setPref(in nsIVariant aGroup, in AString aName, in nsIVariant aValue, in nsILoadContext aPrivacyContext);
   
   /**
    * Check whether or not a pref exists.
    *
    * @param    aGroup      the group for which to check for the pref, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to check for the global pref (applies to all sites)
    * @param    aName       the name of the pref to check for
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to search in memory or in
+   *                       permanent storage for it), obtained from a relevant
+   *                       window or channel.
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
-  boolean hasPref(in nsIVariant aGroup, in AString aName);
+  boolean hasPref(in nsIVariant aGroup, in AString aName, in nsILoadContext aContext);
 
   /**
    * Check whether or not the value of a pref (or its non-existance) is cached.
    *
    * @param    aGroup      the group for which to check for the pref, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to check for the global pref (applies to all sites)
    * @param    aName       the name of the pref to check for
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to search in memory or in
+   *                       permanent storage for it), obtained from a relevant
+   *                       window or channel.
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
-  boolean hasCachedPref(in nsIVariant aGroup, in AString aName);
+  boolean hasCachedPref(in nsIVariant aGroup, in AString aName, in nsILoadContext aContext);
 
   /**
    * Remove a pref.
    *
    * @param    aGroup      the group for which to remove the pref, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to remove the global pref (applies to all sites) 
    * @param    aName       the name of the pref to remove
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to search in memory or in
+   *                       permanent storage for it), obtained from a relevant
+   *                       window or channel.
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
-  void removePref(in nsIVariant aGroup, in AString aName);
+  void removePref(in nsIVariant aGroup, in AString aName, in nsILoadContext aContext);
 
   /**
    * Remove all grouped prefs.  Useful for removing references to the sites
    * the user has visited when the user clears their private data.
+   *
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to remove prefs in memory or
+   *                       in permanent storage), obtained from a relevant
+   *                       window or channel.
    */
-  void removeGroupedPrefs();
+  void removeGroupedPrefs(in nsILoadContext aContext);
 
   /**
    * Remove all prefs with the given name.
    *
    * @param    aName        the setting name for which to remove prefs
+   * @param    aPrivacyContext
+   *                        a context from which to determine the privacy status
+   *                        of the prefs (ie. whether to remove prefs in memory or
+   *                        in permanent storage), obtained from a relevant
+   *                        window or channel.
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
-  void removePrefsByName(in AString aName);
+  void removePrefsByName(in AString aName, in nsILoadContext aContext);
 
   /**
    * Get the prefs that apply to the given site.
    *
    * @param    aGroup      the group for which to retrieve prefs, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to get the global prefs (apply to all sites) 
+   * @param    aPrivacyContext
+   *                       a context from which to determine the privacy status
+   *                       of the pref (ie. whether to search for prefs in memory
+   *                       or in permanent storage), obtained from a relevant
+   *                       window or channel.
    * 
    * @returns  a property bag of prefs
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    */
-  nsIPropertyBag2 getPrefs(in nsIVariant aGroup);
+  nsIPropertyBag2 getPrefs(in nsIVariant aGroup, in nsILoadContext aContext);
 
   /**
    * Get the prefs with the given name.
    *
    * @param    aName        the setting name for which to retrieve prefs
+   * @param    aPrivacyContext
+   *                        a context from which to determine the privacy status
+   *                        of the pref (ie. whether to search for prefs in memory
+   *                        or in permanent storage), obtained from a relevant
+   *                        window or channel.
    * 
    * @returns  a property bag of prefs
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
-  nsIPropertyBag2 getPrefsByName(in AString aName);
+  nsIPropertyBag2 getPrefsByName(in AString aName, in nsILoadContext aContext);
   
   /**
    * Add an observer.
    * 
    * @param    aName       the setting to observe, or null to add
    *                       a generic observer that observes all settings
    * @param    aObserver   the observer to add
    */
--- a/editor/composer/src/nsEditorSpellCheck.cpp
+++ b/editor/composer/src/nsEditorSpellCheck.cpp
@@ -20,16 +20,17 @@
 #include "nsIContent.h"                 // for nsIContent
 #include "nsIContentPrefService.h"      // for nsIContentPrefService, etc
 #include "nsIDOMDocument.h"             // for nsIDOMDocument
 #include "nsIDOMElement.h"              // for nsIDOMElement
 #include "nsIDOMRange.h"                // for nsIDOMRange
 #include "nsIDocument.h"                // for nsIDocument
 #include "nsIEditor.h"                  // for nsIEditor
 #include "nsIHTMLEditor.h"              // for nsIHTMLEditor
+#include "nsILoadContext.h"
 #include "nsISelection.h"               // for nsISelection
 #include "nsISpellChecker.h"            // for nsISpellChecker, etc
 #include "nsISupportsBase.h"            // for nsISupports
 #include "nsISupportsUtils.h"           // for NS_ADDREF
 #include "nsITextServicesDocument.h"    // for nsITextServicesDocument
 #include "nsITextServicesFilter.h"      // for nsITextServicesFilter
 #include "nsIURI.h"                     // for nsIURI
 #include "nsIVariant.h"                 // for nsIWritableVariant, etc
@@ -103,16 +104,31 @@ LastDictionary::GetDocumentURI(nsIEditor
   nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
   NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
 
   *aURI = docUri;
   NS_ADDREF(*aURI);
   return NS_OK;
 }
 
+static already_AddRefed<nsILoadContext>
+GetLoadContext(nsIEditor* aEditor)
+{
+  nsCOMPtr<nsIDOMDocument> domDoc;
+  aEditor->GetDocument(getter_AddRefs(domDoc));
+  NS_ENSURE_TRUE(domDoc, nullptr);
+
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
+  NS_ENSURE_TRUE(doc, nullptr);
+
+  nsCOMPtr<nsISupports> container = doc->GetContainer();
+  nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(container);
+  return loadContext.forget();
+}
+
 NS_IMETHODIMP
 LastDictionary::FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
 
   nsCOMPtr<nsIURI> docUri;
@@ -122,20 +138,21 @@ LastDictionary::FetchLastDictionary(nsIE
   nsCOMPtr<nsIContentPrefService> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
 
   nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
   NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
   uri->SetAsISupports(docUri);
 
+  nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
   bool hasPref;
-  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, &hasPref)) && hasPref) {
+  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, loadContext, &hasPref)) && hasPref) {
     nsCOMPtr<nsIVariant> pref;
-    contentPrefService->GetPref(uri, CPS_PREF_NAME, nullptr, getter_AddRefs(pref));
+    contentPrefService->GetPref(uri, CPS_PREF_NAME, loadContext, nullptr, getter_AddRefs(pref));
     pref->GetAsAString(aDictionary);
   } else {
     aDictionary.Truncate();
   }
 
   return NS_OK;
 }
 
@@ -157,17 +174,18 @@ LastDictionary::StoreCurrentDictionary(n
   nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
   NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
   prefValue->SetAsAString(aDictionary);
 
   nsCOMPtr<nsIContentPrefService> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
-  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue);
+  nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
+  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue, loadContext);
 }
 
 NS_IMETHODIMP
 LastDictionary::ClearCurrentDictionary(nsIEditor* aEditor)
 {
   NS_ENSURE_ARG_POINTER(aEditor);
 
   nsresult rv;
@@ -179,17 +197,18 @@ LastDictionary::ClearCurrentDictionary(n
   nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
   NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
   uri->SetAsISupports(docUri);
 
   nsCOMPtr<nsIContentPrefService> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
 
-  return contentPrefService->RemovePref(uri, CPS_PREF_NAME);
+  nsCOMPtr<nsILoadContext> loadContext = GetLoadContext(aEditor);
+  return contentPrefService->RemovePref(uri, CPS_PREF_NAME, loadContext);
 }
 
 LastDictionary* nsEditorSpellCheck::gDictionaryStore = nullptr;
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
 
 NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -5829,21 +5829,23 @@ var PermissionsHelper = {
             permissions: permissions
           }
         });
         break;
  
       case "Permissions:Clear":
         // An array of the indices of the permissions we want to clear
         let permissionsToClear = JSON.parse(aData);
+        let privacyContext = BrowserApp.selectedBrowser.docShell
+                               .QueryInterface(Ci.nsILoadContext);
 
         for (let i = 0; i < permissionsToClear.length; i++) {
           let indexToClear = permissionsToClear[i];
           let permissionType = this._currentPermissions[indexToClear]["type"];
-          this.clearPermission(uri, permissionType);
+          this.clearPermission(uri, permissionType, privacyContext);
         }
         break;
     }
   },
 
   /**
    * Gets the permission value stored for a specified permission type.
    *
@@ -5878,31 +5880,31 @@ var PermissionsHelper = {
 
   /**
    * Clears a user-set permission value for the site given a permission type.
    *
    * @param aType
    *        The permission type string stored in permission manager.
    *        e.g. "geolocation", "indexedDB", "popup"
    */
-  clearPermission: function clearPermission(aURI, aType) {
+  clearPermission: function clearPermission(aURI, aType, aContext) {
     // Password saving isn't a nsIPermissionManager permission type, so handle
     // it seperately.
     if (aType == "password") {
       // Get rid of exisiting stored logings
       let logins = Services.logins.findLogins({}, aURI.prePath, "", "");
       for (let i = 0; i < logins.length; i++) {
         Services.logins.removeLogin(logins[i]);
       }
       // Re-set login saving to enabled
       Services.logins.setLoginSavingEnabled(aURI.prePath, true);
     } else {
       Services.perms.remove(aURI.host, aType);
       // Clear content prefs set in ContentPermissionPrompt.js
-      Services.contentPrefs.removePref(aURI, aType + ".request.remember");
+      Services.contentPrefs.removePref(aURI, aType + ".request.remember", aContext);
     }
   }
 };
 
 var MasterPassword = {
   pref: "privacy.masterpassword.enabled",
   _tokenName: "",
 
--- a/mobile/android/chrome/content/sanitize.js
+++ b/mobile/android/chrome/content/sanitize.js
@@ -96,17 +96,17 @@ Sanitizer.prototype = {
 
     siteSettings: {
       clear: function ()
       {
         // Clear site-specific permissions like "Allow this site to open popups"
         Services.perms.removeAll();
 
         // Clear site-specific settings like page-zoom level
-        Services.contentPrefs.removeGroupedPrefs();
+        Services.contentPrefs.removeGroupedPrefs(null);
 
         // Clear "Never remember passwords for this site", which is not handled by
         // the permission manager
         var hosts = Services.logins.getAllDisabledHosts({})
         for each (var host in hosts) {
           Services.logins.setLoginSavingEnabled(host, true);
         }
       },
--- a/services/common/preferences.js
+++ b/services/common/preferences.js
@@ -21,16 +21,18 @@ const MIN_INT = -MAX_INT;
 function Preferences(args) {
     if (isObject(args)) {
       if (args.branch)
         this._prefBranch = args.branch;
       if (args.defaultBranch)
         this._defaultBranch = args.defaultBranch;
       if (args.site)
         this._site = args.site;
+      if (args.privacyContext)
+        this._privacyContext = args.privacyContext;
     }
     else if (args)
       this._prefBranch = args;
 }
 
 Preferences.prototype = {
   /**
    * Get the value of a pref, if any; otherwise return the default value.
@@ -71,17 +73,17 @@ Preferences.prototype = {
         // This should never happen.
         throw "Error getting pref " + prefName + "; its value's type is " +
               this._prefSvc.getPrefType(prefName) + ", which I don't know " +
               "how to handle.";
     }
   },
 
   _siteGet: function(prefName, defaultValue) {
-    let value = this._contentPrefSvc.getPref(this._site, this._prefBranch + prefName);
+    let value = this._contentPrefSvc.getPref(this._site, this._prefBranch + prefName, this._privacyContext);
     return typeof value != "undefined" ? value : defaultValue;
   },
 
   /**
    * Set a preference to a value.
    *
    * You can set multiple prefs by passing an object as the only parameter.
    * In that case, this method will treat the properties of the object
@@ -155,17 +157,17 @@ Preferences.prototype = {
 
       default:
         throw "can't set pref " + prefName + " to value '" + prefValue +
               "'; it isn't a String, Number, or Boolean";
     }
   },
 
   _siteSet: function(prefName, prefValue) {
-    this._contentPrefSvc.setPref(this._site, this._prefBranch + prefName, prefValue);
+    this._contentPrefSvc.setPref(this._site, this._prefBranch + prefName, prefValue, this._privacyContext);
   },
 
   /**
    * Whether or not the given pref has a value.  This is different from isSet
    * because it returns true whether the value of the pref is a default value
    * or a user-set value, while isSet only returns true if the value
    * is a user-set value.
    *
@@ -187,17 +189,17 @@ Preferences.prototype = {
       return this._has(prefName);
   },
 
   _has: function(prefName) {
     return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
   },
 
   _siteHas: function(prefName) {
-    return this._contentPrefSvc.hasPref(this._site, this._prefBranch + prefName);
+    return this._contentPrefSvc.hasPref(this._site, this._prefBranch + prefName, this._privacyContext);
   },
 
   /**
    * Whether or not the given pref has a user-set value.  This is different
    * from |has| because it returns true only if the value of the pref is a user-
    * set value, while |has| returns true if the value of the pref is a default
    * value or a user-set value.
    *
@@ -248,17 +250,17 @@ Preferences.prototype = {
       // other exceptions, however, so callers know about them, since we don't
       // know what other exceptions might be thrown and what they might mean.
       if (ex.result != Cr.NS_ERROR_UNEXPECTED)
         throw ex;
     }
   },
 
   _siteReset: function(prefName) {
-    return this._contentPrefSvc.removePref(this._site, this._prefBranch + prefName);
+    return this._contentPrefSvc.removePref(this._site, this._prefBranch + prefName, this._privacyContext);
   },
 
   /**
    * Lock a pref so it can't be changed.
    *
    * @param   prefName  {String|Array}
    *          the pref to lock, or an array of prefs to lock
    */
@@ -382,20 +384,20 @@ Preferences.prototype = {
   },
 
   /**
    * The branch of the preferences tree to which this instance provides access.
    * @private
    */
   _prefBranch: "",
 
-  site: function(site) {
+  site: function(site, privacyContext) {
     if (!(site instanceof Ci.nsIURI))
       site = this._ioSvc.newURI("http://" + site, null, null);
-    return new Preferences({ branch: this._prefBranch, site: site });
+    return new Preferences({ branch: this._prefBranch, site: site, privacyContext: privacyContext });
   },
 
   /**
    * Preferences Service
    * @private
    */
   get _prefSvc() {
     let prefSvc = Cc["@mozilla.org/preferences-service;1"]
--- a/services/common/tests/unit/test_preferences.js
+++ b/services/common/tests/unit/test_preferences.js
@@ -353,17 +353,17 @@ add_test(function test_lock_prefs() {
   // Clean up.
   Preferences.reset("toolkit.defaultChromeURI");
 
   run_next_test();
 });
 */
 
 add_test(function test_site_prefs() {
-  let prefs = Preferences.site("www.example.com");
+  let prefs = Preferences.site("www.example.com", null);
 
   prefs.set("test_site_prefs.integer", 1);
   do_check_eq(prefs.get("test_site_prefs.integer"), 1);
   do_check_true(prefs.has("test_site_prefs.integer"));
   do_check_false(Preferences.has("test_site_prefs.integer"));
   prefs.reset("test_site_prefs.integer");
   do_check_false(prefs.has("test_site_prefs.integer"));
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/ContentPrefInstance.jsm
@@ -0,0 +1,74 @@
+/* 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/. */
+
+'use strict';
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var EXPORTED_SYMBOLS = ['ContentPrefInstance'];
+
+// This is a wrapper for nsIContentPrefService that alleviates the need to pass
+// an nsILoadContext argument to every method. Pass the context to the constructor
+// instead and continue on your way in blissful ignorance.
+
+function ContentPrefInstance(aContext) {
+  this._contentPrefSvc = Cc["@mozilla.org/content-pref/service;1"].
+                           getService(Ci.nsIContentPrefService);
+  this._context = aContext;
+}
+
+ContentPrefInstance.prototype = {
+  getPref: function ContentPrefInstance_init(aName, aGroup, aCallback) {
+    return this._contentPrefSvc.getPref(aName, aGroup, this._context, aCallback);
+  },
+
+  setPref: function ContentPrefInstance_setPref(aGroup, aName, aValue) {
+    return this._contentPrefSvc.setPref(aGroup, aName, aValue, this._context);
+  },
+
+  hasPref: function ContentPrefInstance_hasPref(aGroup, aName) {
+    return this._contentPrefSvc.hasPref(aGroup, aName, this._context);
+  },
+
+  hasCachedPref: function ContentPrefInstance_hasCachedPref(aGroup, aName) {
+    return this._contentPrefSvc.hasCachedPref(aGroup, aName, this._context);
+  },
+
+  removePref: function ContentPrefInstance_removePref(aGroup, aName) {
+    return this._contentPrefSvc.removePref(aGroup, aName, this._context);
+  },
+
+  removeGroupedPrefs: function ContentPrefInstance_removeGroupedPrefs() {
+    return this._contentPrefSvc.removeGroupedPrefs(this._context);
+  },
+
+  removePrefsByName: function ContentPrefInstance_removePrefsByName(aName) {
+    return this._contentPrefSvc.removePrefsByName(aName, this._context);
+  },
+
+  getPrefs: function ContentPrefInstance_getPrefs(aGroup) {
+    return this._contentPrefSvc.getPrefs(aGroup, this._context);
+  },
+
+  getPrefsByName: function ContentPrefInstance_getPrefsByName(aName) {
+    return this._contentPrefSvc.getPrefsByName(aName, this._context);
+  },
+
+  addObserver: function ContentPrefInstance_addObserver(aName, aObserver) {
+    return this._contentPrefSvc.addObserver(aName, aObserver);
+  },
+
+  removeObserver: function ContentPrefInstance_removeObserver(aName, aObserver) {
+    return this._contentPrefSvc.removeObserver(aName, aObserver);
+  },
+
+  get grouper() {
+    return this._contentPrefSvc.grouper;
+  },
+
+  get DBConnection() {
+    return this._contentPrefSvc.DBConnection;
+  }
+};
--- a/toolkit/components/contentprefs/Makefile.in
+++ b/toolkit/components/contentprefs/Makefile.in
@@ -8,11 +8,13 @@ srcdir = @srcdir@
 VPATH = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = contentprefs
 
 EXTRA_COMPONENTS = nsContentPrefService.js nsContentPrefService.manifest
 
+EXTRA_JS_MODULES = ContentPrefInstance.jsm
+
 TEST_DIRS += tests
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/components/contentprefs/nsContentPrefService.js
+++ b/toolkit/components/contentprefs/nsContentPrefService.js
@@ -95,25 +95,17 @@ function ContentPrefService() {
   electrolify(this);
 
   // If this throws an exception, it causes the getService call to fail,
   // but the next time a consumer tries to retrieve the service, we'll try
   // to initialize the database again, which might work if the failure
   // was due to a temporary condition (like being out of disk space).
   this._dbInit();
 
-  // detect if we are in private browsing mode
-  this._inPrivateBrowsing = false;
-  // The Private Browsing service might not be available.
-  if (["@mozilla.org/privatebrowsing;1"] in Cc) {
-    var pbs = Cc["@mozilla.org/privatebrowsing;1"].
-                getService(Ci.nsIPrivateBrowsingService);
-    this._inPrivateBrowsing = pbs.privateBrowsingEnabled;
-  }
-  this._observerSvc.addObserver(this, "private-browsing", false);
+  this._observerSvc.addObserver(this, "last-pb-context-exited", false);
 
   // Observe shutdown so we can shut down the database connection.
   this._observerSvc.addObserver(this, "xpcom-shutdown", false);
 }
 
 var inMemoryPrefsProto = {
   getPref: function(aName, aGroup) {
     aGroup = aGroup || "__GlobalPrefs__";
@@ -160,17 +152,17 @@ var inMemoryPrefsProto = {
     }
   }
 };
 
 ContentPrefService.prototype = {
   //**************************************************************************//
   // XPCOM Plumbing
 
-  classID:          Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
+  classID:          Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),
   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentPrefService,
                                            Ci.nsIMessageListener]),
 
 
   //**************************************************************************//
   // Convenience Getters
 
   // Observer Service
@@ -201,17 +193,17 @@ ContentPrefService.prototype = {
   },
 
 
   //**************************************************************************//
   // Destruction
 
   _destroy: function ContentPrefService__destroy() {
     this._observerSvc.removeObserver(this, "xpcom-shutdown");
-    this._observerSvc.removeObserver(this, "private-browsing");
+    this._observerSvc.removeObserver(this, "last-pb-context-exited");
 
     // Finalize statements which may have been used asynchronously.
     // FIXME(696499): put them in an object cache like other components.
     if (this.__stmtSelectPrefID) {
       this.__stmtSelectPrefID.finalize();
       this.__stmtSelectPrefID = null;
     }
     if (this.__stmtSelectGlobalPrefID) {
@@ -288,26 +280,18 @@ ContentPrefService.prototype = {
   //**************************************************************************//
   // nsIObserver
 
   observe: function ContentPrefService_observe(subject, topic, data) {
     switch (topic) {
       case "xpcom-shutdown":
         this._destroy();
         break;
-      case "private-browsing":
-        switch (data) {
-          case "enter":
-            this._inPrivateBrowsing = true;
-            break;
-          case "exit":
-            this._inPrivateBrowsing = false;
-            this._privModeStorage.invalidate();
-            break;
-        }
+      case "last-pb-context-exited":
+        this._privModeStorage.invalidate();
         break;
     }
   },
 
 
   //**************************************************************************//
   // Prefs cache
   _cache: Object.create(inMemoryPrefsProto, {
@@ -389,24 +373,24 @@ ContentPrefService.prototype = {
         return res;
       }
     }
   }),
 
   //**************************************************************************//
   // nsIContentPrefService
 
-  getPref: function ContentPrefService_getPref(aGroup, aName, aCallback) {
+  getPref: function ContentPrefService_getPref(aGroup, aName, aContext, aCallback) {
     if (!aName)
       throw Components.Exception("aName cannot be null or an empty string",
                                  Cr.NS_ERROR_ILLEGAL_VALUE);
 
     var group = this._parseGroupParam(aGroup);
 
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
       let [haspref, value] = this._privModeStorage.getPref(aName, group);
       if (haspref) {
         if (aCallback) {
           this._scheduleCallback(function(){aCallback.onResult(value);});
           return;
         }
         return value;
       }
@@ -414,27 +398,27 @@ ContentPrefService.prototype = {
       // session, to try to get one from normal mode
     }
 
     if (group == null)
       return this._selectGlobalPref(aName, aCallback);
     return this._selectPref(group, aName, aCallback);
   },
 
-  setPref: function ContentPrefService_setPref(aGroup, aName, aValue) {
+  setPref: function ContentPrefService_setPref(aGroup, aName, aValue, aContext) {
     // If the pref is already set to the value, there's nothing more to do.
-    var currentValue = this.getPref(aGroup, aName);
+    var currentValue = this.getPref(aGroup, aName, aContext);
     if (typeof currentValue != "undefined") {
       if (currentValue == aValue)
         return;
     }
 
     var group = this._parseGroupParam(aGroup);
 
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
       this._privModeStorage.setPref(aName, aValue, group);
       this._notifyPrefSet(group, aName, aValue);
       return;
     }
 
     var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
     var groupID, prefID;
     if (group == null) {
@@ -452,41 +436,41 @@ ContentPrefService.prototype = {
     else
       this._insertPref(groupID, settingID, aValue);
 
     this._cache.setPref(aName, aValue, group);
 
     this._notifyPrefSet(group, aName, aValue);
   },
 
-  hasPref: function ContentPrefService_hasPref(aGroup, aName) {
+  hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) {
     // XXX If consumers end up calling this method regularly, then we should
     // optimize this to query the database directly.
-    return (typeof this.getPref(aGroup, aName) != "undefined");
+    return (typeof this.getPref(aGroup, aName, aContext) != "undefined");
   },
 
-  hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName) {
+  hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName, aContext) {
     if (!aName)
       throw Components.Exception("aName cannot be null or an empty string",
                                  Cr.NS_ERROR_ILLEGAL_VALUE);
 
     let group = this._parseGroupParam(aGroup);
-    let storage = this._inPrivateBrowsing? this._privModeStorage: this._cache;
+    let storage = aContext && aContext.usePrivateBrowsing ? this._privModeStorage: this._cache;
     let [cached,] = storage.getPref(aName, group);
     return cached;
   },
 
-  removePref: function ContentPrefService_removePref(aGroup, aName) {
+  removePref: function ContentPrefService_removePref(aGroup, aName, aContext) {
     // If there's no old value, then there's nothing to remove.
-    if (!this.hasPref(aGroup, aName))
+    if (!this.hasPref(aGroup, aName, aContext))
       return;
 
     var group = this._parseGroupParam(aGroup);
 
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
       this._privModeStorage.removePref(aName, group);
       this._notifyPrefRemoved(group, aName);
       return;
     }
 
     var settingID = this._selectSettingID(aName);
     var groupID, prefID;
     if (group == null) {
@@ -504,19 +488,19 @@ ContentPrefService.prototype = {
     this._deleteSettingIfUnused(settingID);
     if (groupID)
       this._deleteGroupIfUnused(groupID);
 
     this._cache.removePref(aName, group);
     this._notifyPrefRemoved(group, aName);
   },
 
-  removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs() {
+  removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) {
     // will not delete global preferences
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
         // keep only global prefs
         this._privModeStorage.invalidate(true);
     }
     this._cache.invalidate(true);
     this._dbConnection.beginTransaction();
     try {
       this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
       this._dbConnection.executeSimpleSQL("DELETE FROM groups");
@@ -527,22 +511,22 @@ ContentPrefService.prototype = {
       this._dbConnection.commitTransaction();
     }
     catch(ex) {
       this._dbConnection.rollbackTransaction();
       throw ex;
     }
   },
 
-  removePrefsByName: function ContentPrefService_removePrefsByName(aName) {
+  removePrefsByName: function ContentPrefService_removePrefsByName(aName, aContext) {
     if (!aName)
       throw Components.Exception("aName cannot be null or an empty string",
                                  Cr.NS_ERROR_ILLEGAL_VALUE);
 
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
       let groupNames = this._privModeStorage.groupsForName(aName);
       for (var i = 0; i < groupNames.length; i++) {
         let groupName = groupNames[i];
         this._privModeStorage.removePref(aName, groupName);
         this._notifyPrefRemoved(groupName, aName);
       }
     }
 
@@ -577,62 +561,59 @@ ContentPrefService.prototype = {
 
     this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
     this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);
 
     for (var i = 0; i < groupNames.length; i++) {
       this._cache.removePref(aName, groupNames[i]);
       if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
         this._deleteGroupIfUnused(groupIDs[i]);
-      if (!this._inPrivateBrowsing) {
+      if (!aContext || !aContext.usePrivateBrowsing) {
         this._notifyPrefRemoved(groupNames[i], aName);
       }
     }
   },
 
-  getPrefs: function ContentPrefService_getPrefs(aGroup) {
+  getPrefs: function ContentPrefService_getPrefs(aGroup, aContext) {
     var group = this._parseGroupParam(aGroup);
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
         let prefs = Cc["@mozilla.org/hash-property-bag;1"].
                     createInstance(Ci.nsIWritablePropertyBag);
         let [hasbranch,properties] = this._privModeStorage.getPrefs(group);
         for (let [entry, value] of properties) {
           prefs.setProperty(entry, value);
         }
         return prefs;
     }
 
     if (group == null)
       return this._selectGlobalPrefs();
     return this._selectPrefs(group);
   },
 
-  getPrefsByName: function ContentPrefService_getPrefsByName(aName) {
+  getPrefsByName: function ContentPrefService_getPrefsByName(aName, aContext) {
     if (!aName)
       throw Components.Exception("aName cannot be null or an empty string",
                                  Cr.NS_ERROR_ILLEGAL_VALUE);
 
-    if (this._inPrivateBrowsing) {
+    if (aContext && aContext.usePrivateBrowsing) {
       let prefs = Cc["@mozilla.org/hash-property-bag;1"].
                   createInstance(Ci.nsIWritablePropertyBag);
       let groupNames = this._privModeStorage.groupsForName(aName);
       for (var i = 0; i < groupNames.length; i++) {
         let groupName = groupNames[i];
         prefs.setProperty(groupName,
                           this._privModeStorage.getPref(aName, groupName)[1]);
       }
       return prefs;
     }
 
     return this._selectPrefsByName(aName);
   },
 
-  // boolean to indicate if we are in private browsing mode
-  _inPrivateBrowsing: false,
-
   // A hash of arrays of observers, indexed by setting name.
   _observers: {},
 
   // An array of generic observers, which observe all settings.
   _genericObservers: [],
 
   addObserver: function ContentPrefService_addObserver(aName, aObserver) {
     var observers;
--- a/toolkit/components/contentprefs/nsContentPrefService.manifest
+++ b/toolkit/components/contentprefs/nsContentPrefService.manifest
@@ -1,6 +1,6 @@
-component {e6a3f533-4ffa-4615-8eb4-d4e72d883fa7} nsContentPrefService.js
-contract @mozilla.org/content-pref/service;1 {e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}
+component {e3f772f3-023f-4b32-b074-36cf0fd5d414} nsContentPrefService.js
+contract @mozilla.org/content-pref/service;1 {e3f772f3-023f-4b32-b074-36cf0fd5d414}
 component {8df290ae-dcaa-4c11-98a5-2429a4dc97bb} nsContentPrefService.js
 contract @mozilla.org/content-pref/hostname-grouper;1 {8df290ae-dcaa-4c11-98a5-2429a4dc97bb}
 category wakeup-request nsContentPrefService @mozilla.org/content-pref/service;1,nsIContentPrefService,getService,ContentPref:getPref,ContentPref:setPref
 
--- a/toolkit/components/contentprefs/tests/unit/head_contentPrefs.js
+++ b/toolkit/components/contentprefs/tests/unit/head_contentPrefs.js
@@ -4,16 +4,19 @@
 
 // Inspired by the Places infrastructure in head_bookmarks.js
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/ContentPrefInstance.jsm');
+
 const CONTENT_PREFS_DB_FILENAME = "content-prefs.sqlite";
 const CONTENT_PREFS_BACKUP_DB_FILENAME = "content-prefs.sqlite.corrupt";
 
 var ContentPrefTest = {
   //**************************************************************************//
   // Convenience Getters
 
   __dirSvc: null,
@@ -128,16 +131,25 @@ var ContentPrefTest = {
   log: function ContentPrefTest_log(message) {
     message = "*** ContentPrefTest: " + message;
     this._consoleSvc.logStringMessage(message);
     print(message);
   }
 
 };
 
+let gInPrivateBrowsing = false;
+function enterPBMode() {
+  gInPrivateBrowsing = true;
+}
+function exitPBMode() {
+  gInPrivateBrowsing = false;
+  Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+}
+
 ContentPrefTest.deleteDatabase();
 
 function inChildProcess() {
   var appInfo = Cc["@mozilla.org/xre/app-info;1"];
   if (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType ==
       Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
     return false;
   }
--- a/toolkit/components/contentprefs/tests/unit/test_bug248970.js
+++ b/toolkit/components/contentprefs/tests/unit/test_bug248970.js
@@ -1,71 +1,48 @@
 /* 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/. */
 
-var _PBSvc = null;
-function get_PBSvc() {
-  if (_PBSvc)
-    return _PBSvc;
-
-  try {
-    _PBSvc = Cc["@mozilla.org/privatebrowsing;1"].
-             getService(Ci.nsIPrivateBrowsingService);
-    return _PBSvc;
-  } catch (e) {}
-  return null;
-}
-
-var _CMSvc = null;
-function get_ContentPrefs() {
-  if (_CMSvc)
-    return _CMSvc;
-
-  return Cc["@mozilla.org/content-pref/service;1"].
-         createInstance(Ci.nsIContentPrefService);
-}
-
 function run_test() {
-  var pb = get_PBSvc();
-  if (pb) { // Private Browsing might not be available
     var prefBranch = Cc["@mozilla.org/preferences-service;1"].
                      getService(Ci.nsIPrefBranch);
     prefBranch.setBoolPref("browser.privatebrowsing.keep_current_session", true);
+    
+    let loadContext = { get usePrivateBrowsing() { return gInPrivateBrowsing; } };
 
     ContentPrefTest.deleteDatabase();
-    var cp = get_ContentPrefs();
+    var cp = new ContentPrefInstance(loadContext);
     do_check_neq(cp, null, "Retrieving the content prefs service failed");
 
     try {
       const uri1 = ContentPrefTest.getURI("http://www.example.com/");
       const uri2 = ContentPrefTest.getURI("http://www.anotherexample.com/");
       const pref_name = "browser.content.full-zoom";
       const zoomA = 1.5, zoomA_new = 0.8, zoomB = 1.3;
       // save Zoom-A
       cp.setPref(uri1, pref_name, zoomA);
       // make sure Zoom-A is retrievable
       do_check_eq(cp.getPref(uri1, pref_name), zoomA);
       // enter private browsing mode
-      pb.privateBrowsingEnabled = true;
+      enterPBMode();
       // make sure Zoom-A is retrievable
       do_check_eq(cp.getPref(uri1, pref_name), zoomA);
       // save Zoom-B
       cp.setPref(uri2, pref_name, zoomB);
       // make sure Zoom-B is retrievable
       do_check_eq(cp.getPref(uri2, pref_name), zoomB);
       // update Zoom-A
       cp.setPref(uri1, pref_name, zoomA_new);
       // make sure Zoom-A has changed
       do_check_eq(cp.getPref(uri1, pref_name), zoomA_new);
       // exit private browsing mode
-      pb.privateBrowsingEnabled = false;
+      exitPBMode();
       // make sure Zoom-A change has not persisted
       do_check_eq(cp.getPref(uri1, pref_name), zoomA);
       // make sure Zoom-B change has not persisted
       do_check_eq(cp.hasPref(uri2, pref_name), false);
     } catch (e) {
       do_throw("Unexpected exception: " + e);
     }
 
     prefBranch.clearUserPref("browser.privatebrowsing.keep_current_session");
-  }
 }
--- a/toolkit/components/contentprefs/tests/unit/test_bug503971.js
+++ b/toolkit/components/contentprefs/tests/unit/test_bug503971.js
@@ -1,15 +1,14 @@
 /* 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/. */
 
 function run_test() {
-  var cps = Cc["@mozilla.org/content-pref/service;1"].
-            getService(Ci.nsIContentPrefService);
+  var cps = new ContentPrefInstance(null);
 
   var uri = ContentPrefTest.getURI("http://www.example.com/");
   
   do_check_thrown(function () { cps.setPref(uri, null, 8); });
   do_check_thrown(function () { cps.hasPref(uri, null); });
   do_check_thrown(function () { cps.getPref(uri, null); });
   do_check_thrown(function () { cps.removePref(uri, null); });
   do_check_thrown(function () { cps.getPrefsByName(null); });
--- a/toolkit/components/contentprefs/tests/unit/test_bug679784.js
+++ b/toolkit/components/contentprefs/tests/unit/test_bug679784.js
@@ -10,43 +10,37 @@ var prefObserver = {
     },
     removedCalledNum: 0,
     onContentPrefRemoved: function(aGroup, aName) {
         this.removedCalledNum++;
     }
 };
 
 function run_test() {
-  var pbs;
-  try {
-    pbs = Cc["@mozilla.org/privatebrowsing;1"].getService(Ci.nsIPrivateBrowsingService);
-  } catch(e) {
-    // Private Browsing might not be available
-    return;
-  }
-
   Cc["@mozilla.org/preferences-service;1"].
     getService(Ci.nsIPrefBranch).
       setBoolPref("browser.privatebrowsing.keep_current_session", true);
+  
+  let loadContext = { get usePrivateBrowsing() { return gInPrivateBrowsing; } };
 
-  var cps = Cc["@mozilla.org/content-pref/service;1"].getService(Ci.nsIContentPrefService);
+  var cps = new ContentPrefInstance(loadContext);
   cps.removeGroupedPrefs();
 
   var uri = ContentPrefTest.getURI("http://www.example.com/");
   var group = cps.grouper.group(uri);
 
   // first, set a pref in normal mode
   cps.setPref(uri, "value", "foo");
   cps.setPref(null, "value-global", "foo-global");
 
   var num;
   cps.addObserver("value", prefObserver);
   cps.addObserver("value-global", prefObserver);
 
-  pbs.privateBrowsingEnabled = true;
+  enterPBMode();
 
   // test setPref
   num = prefObserver.setCalledNum;
   cps.setPref(uri, "value", "foo-private-browsing");
   do_check_eq(cps.hasPref(uri, "value"), true);
   do_check_eq(cps.getPref(uri, "value"), "foo-private-browsing");
   do_check_eq(prefObserver.setCalledNum, num + 1);
 
--- a/toolkit/components/contentprefs/tests/unit/test_contentPrefs.js
+++ b/toolkit/components/contentprefs/tests/unit/test_contentPrefs.js
@@ -98,18 +98,17 @@ function run_test() {
     do_check_true(backupDBFile.exists());
     do_check_true(cps.DBConnection.connectionReady);
 
     cps.DBConnection.close();
   }
 
 
   // Now get the content pref service for real for use by the rest of the tests.
-  var cps = Cc["@mozilla.org/content-pref/service;1"].
-            getService(Ci.nsIContentPrefService);
+  let cps = new ContentPrefInstance(null);
 
   var uri = ContentPrefTest.getURI("http://www.example.com/");
 
   // Make sure disk synchronization checking is turned off by default.
   var statement = cps.DBConnection.createStatement("PRAGMA synchronous");
   statement.executeStep();
   do_check_eq(0, statement.getInt32(0));
 
--- a/toolkit/components/contentprefs/tests/unit/test_contentPrefsCache.js
+++ b/toolkit/components/contentprefs/tests/unit/test_contentPrefsCache.js
@@ -1,15 +1,14 @@
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-let cps = Cc["@mozilla.org/content-pref/service;1"].
-          getService(Ci.nsIContentPrefService);
+let cps = new ContentPrefInstance(null);
 
 function run_test() {
   testCacheWorks("test1.example.com", "test-pref1");
   testHasCachedPrefFunction("test2.example.com", "test-pref2");
   testSetCaches("test3.example.com", "test-pref3");
   testGetCaches("test4.example.com", "test-pref4");
   testRemovePrefs("test5.example.com", "test-pref5");
   testTypeConversions("test6.example.com", "test-pref6");
--- a/toolkit/components/contentprefs/tests/unit/test_getPrefAsync.js
+++ b/toolkit/components/contentprefs/tests/unit/test_getPrefAsync.js
@@ -1,13 +1,12 @@
 /* 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/. */
-var cps = Cc["@mozilla.org/content-pref/service;1"].
-          getService(Ci.nsIContentPrefService);
+var cps = new ContentPrefInstance(null);
 var uri = ContentPrefTest.getURI("http://www.example.com/");
 
 function run_test() {
   do_test_pending();
 
   cps.setPref(uri, "asynctest", "pie");
   do_check_eq(cps.getPref(uri, "asynctest"), "pie");
 
--- a/toolkit/components/contentprefs/tests/unit/test_stringGroups.js
+++ b/toolkit/components/contentprefs/tests/unit/test_stringGroups.js
@@ -1,16 +1,15 @@
 /* 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/. */
 
 function run_test() {
 
-  var cps = Cc["@mozilla.org/content-pref/service;1"].
-            getService(Ci.nsIContentPrefService);
+  var cps = new ContentPrefInstance(null);
 
   // Make sure disk synchronization checking is turned off by default.
   var statement = cps.DBConnection.createStatement("PRAGMA synchronous");
   statement.executeStep();
   do_check_eq(0, statement.getInt32(0));
 
   // These are the different types of aGroup arguments we'll test.
   var anObject = {"foo":"bar"};                                // a simple object
--- a/toolkit/components/contentprefs/tests/unit/test_unusedGroupsAndSettings.js
+++ b/toolkit/components/contentprefs/tests/unit/test_unusedGroupsAndSettings.js
@@ -1,14 +1,13 @@
 /* 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/. */
 
-var cps = Cc["@mozilla.org/content-pref/service;1"].
-          getService(Ci.nsIContentPrefService);
+var cps = new ContentPrefInstance(null);
           
 function run_test() {
   var uri1 = ContentPrefTest.getURI("http://www.domain1.com/");
   var uri2 = ContentPrefTest.getURI("http://foo.domain1.com/");
   var uri3 = ContentPrefTest.getURI("http://domain1.com/");
   var uri4 = ContentPrefTest.getURI("http://www.domain2.com/");
 
   cps.setPref(uri1, "one", 1);
--- a/toolkit/components/contentprefs/tests/unit_ipc/contentPrefs_childipc.js
+++ b/toolkit/components/contentprefs/tests/unit_ipc/contentPrefs_childipc.js
@@ -15,25 +15,25 @@ function run_test() {
   // Cannot set general values
   try {
     cps.setPref("group", "name", "someValue2");
     do_check_false(true, "Must have thrown exception on setting general value");
   }
   catch(e) { }
 
   // Can set&get whitelisted values
-  cps.setPref("group", "browser.upload.lastDir", "childValue");
-  do_check_eq(cps.getPref("group", "browser.upload.lastDir"), "childValue");
+  cps.setPref("group", "browser.upload.lastDir", "childValue", null);
+  do_check_eq(cps.getPref("group", "browser.upload.lastDir", null), "childValue");
 
   // Test sending URI
   var ioSvc = Cc["@mozilla.org/network/io-service;1"].
               getService(Ci.nsIIOService);
   var uri = ioSvc.newURI("http://mozilla.org", null, null);
-  cps.setPref(uri, "browser.upload.lastDir", "childValue2");
-  do_check_eq(cps.getPref(uri, "browser.upload.lastDir"), "childValue2");
+  cps.setPref(uri, "browser.upload.lastDir", "childValue2", null);
+  do_check_eq(cps.getPref(uri, "browser.upload.lastDir", null), "childValue2");
 
   // Previous value
-  do_check_eq(cps.getPref("group", "browser.upload.lastDir"), "childValue");
+  do_check_eq(cps.getPref("group", "browser.upload.lastDir", null), "childValue");
 
   // Tell parent to finish and clean up
   cps.wrappedJSObject.messageManager.sendSyncMessage('ContentPref:QUIT');
 }
 
--- a/toolkit/components/contentprefs/tests/unit_ipc/test_contentPrefs_parentipc.js
+++ b/toolkit/components/contentprefs/tests/unit_ipc/test_contentPrefs_parentipc.js
@@ -16,23 +16,23 @@ function run_test() {
   // Cannot get values
   do_check_false(messageHandler.receiveMessage({
     name: "ContentPref:getPref",
     json: { group: 'group2', name: 'name' } }).succeeded);
 
   // Cannot set general values
   messageHandler.receiveMessage({ name: "ContentPref:setPref",
     json: { group: 'group2', name: 'name', value: 'someValue' } });
-  do_check_eq(cps.getPref('group', 'name'), undefined);
+  do_check_eq(cps.getPref('group', 'name', null), undefined);
 
   // Can set whitelisted values
   do_check_true(messageHandler.receiveMessage({ name: "ContentPref:setPref",
     json: { group: 'group2', name: 'browser.upload.lastDir',
             value: 'someValue' } }).succeeded);
-  do_check_eq(cps.getPref('group2', 'browser.upload.lastDir'), 'someValue');
+  do_check_eq(cps.getPref('group2', 'browser.upload.lastDir', null), 'someValue');
 
   // Prepare for child tests
 
   // Manually listen to messages - the wakeup manager should do this
   // for us, but it doesn't run in xpcshell tests.
   var messageProxy = {
     receiveMessage: function(aMessage) {
       if (aMessage.name == 'ContentPref:QUIT') {
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -192,20 +192,20 @@ var ForgetAboutSite = {
       }
       finally {
         stmt.finalize();
       }
 
       // Now, for each name we got back, remove all of its prefs.
       for (let i = 0; i < names.length; i++) {
         let uri = names[i];
-        let enumerator = cp.getPrefs(uri).enumerator;
+        let enumerator = cp.getPrefs(uri, null).enumerator;
         while (enumerator.hasMoreElements()) {
           let pref = enumerator.getNext().QueryInterface(Ci.nsIProperty);
-          cp.removePref(uri, pref.name);
+          cp.removePref(uri, pref.name, null);
         }
       }
     }
 
     // Indexed DB
     let (idbm = Cc["@mozilla.org/dom/indexeddb/manager;1"].
                 getService(Ci.nsIIndexedDatabaseManager)) {
       // delete data from both HTTP and HTTPS sites
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -283,34 +283,34 @@ function check_permission_exists(aURI, a
  * @param aURI
  *        The URI to add a preference for.
  */
 function add_preference(aURI)
 {
   check_preference_exists(aURI, false);
   let cp = Cc["@mozilla.org/content-pref/service;1"].
            getService(Ci.nsIContentPrefService);
-  cp.setPref(aURI, PREFERENCE_NAME, "foo");
+  cp.setPref(aURI, PREFERENCE_NAME, "foo", null);
   check_preference_exists(aURI, true);
 }
 
 /**
  * Checks to see if a preference exists for the given URI.
  *
  * @param aURI
  *        The URI to check if a preference exists.
  * @param aExists
  *        True if the permission should exist, false otherwise.
  */
 function check_preference_exists(aURI, aExists)
 {
   let cp = Cc["@mozilla.org/content-pref/service;1"].
            getService(Ci.nsIContentPrefService);
   let checker = aExists ? do_check_true : do_check_false;
-  checker(cp.hasPref(aURI, PREFERENCE_NAME));
+  checker(cp.hasPref(aURI, PREFERENCE_NAME, null));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Test Functions
 
 // History
 function test_history_cleared_with_direct_match()
 {
@@ -510,17 +510,17 @@ function test_content_preferecnes_not_cl
   const TEST_URI = uri("http://ilovemozilla.org");
   add_preference(TEST_URI);
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
   check_preference_exists(TEST_URI, true);
 
   // Reset state
   let cp = Cc["@mozilla.org/content-pref/service;1"].
            getService(Ci.nsIContentPrefService);
-  cp.removePref(TEST_URI, PREFERENCE_NAME);
+  cp.removePref(TEST_URI, PREFERENCE_NAME, null);
   check_preference_exists(TEST_URI, false);
 }
 
 // Cache
 function test_cache_cleared()
 {
   // Because this test is asynchronous, it should be the last test
   do_check_eq(tests[tests.length - 1], arguments.callee);
--- a/toolkit/mozapps/downloads/DownloadLastDir.jsm
+++ b/toolkit/mozapps/downloads/DownloadLastDir.jsm
@@ -45,17 +45,20 @@ let observer = {
     switch (aTopic) {
       case "last-pb-context-exited":
         gDownloadLastDirFile = null;
         break;
       case "browser:purge-session-history":
         gDownloadLastDirFile = null;
         if (Services.prefs.prefHasUserValue(LAST_DIR_PREF))
           Services.prefs.clearUserPref(LAST_DIR_PREF);
-        Services.contentPrefs.removePrefsByName(LAST_DIR_PREF);
+        // Ensure that purging session history causes both the session-only PB cache
+        // and persistent prefs to be cleared.
+        Services.contentPrefs.removePrefsByName(LAST_DIR_PREF, {usePrivateBrowsing: false});
+        Services.contentPrefs.removePrefsByName(LAST_DIR_PREF, {usePrivateBrowsing: true});
         break;
     }
   }
 };
 
 let os = Components.classes["@mozilla.org/observer-service;1"]
                    .getService(Components.interfaces.nsIObserverService);
 os.addObserver(observer, "last-pb-context-exited", true);
@@ -92,17 +95,21 @@ DownloadLastDir.prototype = {
   // compat shims
   get file() { return this.getFile(); },
   set file(val) { this.setFile(null, val); },
   cleanupPrivateFile: function () {
     gDownloadLastDirFile = null;
   },
   getFile: function (aURI) {
     if (aURI && isContentPrefEnabled()) {
-      let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF);
+      let loadContext = this.window
+                            .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                            .getInterface(Components.interfaces.nsIWebNavigation)
+                            .QueryInterface(Components.interfaces.nsILoadContext);
+      let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF, loadContext);
       if (lastDir) {
         var lastDirFile = Components.classes["@mozilla.org/file/local;1"]
                                     .createInstance(Components.interfaces.nsIFile);
         lastDirFile.initWithPath(lastDir);
         return lastDirFile;
       }
     }
     if (gDownloadLastDirFile && !gDownloadLastDirFile.exists())
@@ -113,20 +120,24 @@ DownloadLastDir.prototype = {
         gDownloadLastDirFile = readLastDirPref();
       return gDownloadLastDirFile;
     }
     else
       return readLastDirPref();
   },
   setFile: function (aURI, aFile) {
     if (aURI && isContentPrefEnabled()) {
+      let loadContext = this.window
+                            .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                            .getInterface(Components.interfaces.nsIWebNavigation)
+                            .QueryInterface(Components.interfaces.nsILoadContext);
       if (aFile instanceof Components.interfaces.nsIFile)
-        Services.contentPrefs.setPref(aURI, LAST_DIR_PREF, aFile.path);
+        Services.contentPrefs.setPref(aURI, LAST_DIR_PREF, aFile.path, loadContext);
       else
-        Services.contentPrefs.removePref(aURI, LAST_DIR_PREF);
+        Services.contentPrefs.removePref(aURI, LAST_DIR_PREF, loadContext);
     }
     if (this.isPrivate()) {
       if (aFile instanceof Components.interfaces.nsIFile)
         gDownloadLastDirFile = aFile.clone();
       else
         gDownloadLastDirFile = null;
     } else {
       if (aFile instanceof Components.interfaces.nsIFile)