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 111686 53b97b4ec554925108480bb34ecf5cdeb0659a62
parent 111685 ce8e092cb0790b2175bbbca04bf5f55bf64675b6
child 111687 63ce297be1f21ec26001fdcd20b9318fb29b6391
push id17201
push userjosh@joshmatthews.net
push dateMon, 29 Oct 2012 20:23:14 +0000
treeherdermozilla-inbound@53b97b4ec554 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs723002
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)