Bug 722861 - Add privacy information to image requests, and use a separate cache for private requests. r=joe
authorJosh Matthews <josh@joshmatthews.net>
Tue, 26 Jun 2012 00:20:12 -0400
changeset 105631 6b4ab96903f2b3144f5598e2813a3237a515206e
parent 105630 c3333e6e37aa746ccf694a9faa8091d5112988ab
child 105632 da1ba554adb87fe44e9943851db027087d254125
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersjoe
bugs722861
milestone17.0a1
Bug 722861 - Add privacy information to image requests, and use a separate cache for private requests. r=joe
content/base/public/nsContentUtils.h
content/base/src/Makefile.in
content/base/src/nsContentUtils.cpp
content/base/src/nsDocument.cpp
content/base/src/nsImageLoadingContent.cpp
content/base/src/nsObjectLoadingContent.cpp
docshell/base/nsWebNavigationInfo.cpp
embedding/browser/webBrowser/nsContextMenuInfo.cpp
image/build/nsImageModule.cpp
image/public/imgICache.idl
image/public/imgITools.idl
image/src/imgLoader.cpp
image/src/imgLoader.h
image/src/imgRequest.cpp
image/src/imgRequest.h
image/src/imgTools.cpp
image/test/mochitest/imgutils.js
image/test/unit/async_load_tests.js
image/test/unit/test_private_channel.js
layout/build/nsContentDLF.cpp
layout/generic/nsImageFrame.cpp
widget/cocoa/nsMenuItemIconX.mm
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -477,23 +477,16 @@ public:
     return sNameSpaceManager;
   }
 
   static nsIIOService* GetIOService()
   {
     return sIOService;
   }
 
-  static imgILoader* GetImgLoader()
-  {
-    if (!sImgLoaderInitialized)
-      InitImgLoader();
-    return sImgLoader;
-  }
-
 #ifdef MOZ_XTF
   static nsIXTFService* GetXTFService();
 #endif
 
 #ifdef IBMBIDI
   static nsIBidiKeyboard* GetBidiKeyboard();
 #endif
   
@@ -659,19 +652,26 @@ public:
                             nsIDocument* aLoadingDocument,
                             nsIPrincipal* aLoadingPrincipal,
                             nsIURI* aReferrer,
                             imgIDecoderObserver* aObserver,
                             int32_t aLoadFlags,
                             imgIRequest** aRequest);
 
   /**
+   * Obtain an image loader that respects the given document/channel's privacy status.
+   * Null document/channel arguments return the public image loader.
+   */
+  static imgILoader* GetImgLoaderForDocument(nsIDocument* aDoc);
+  static imgILoader* GetImgLoaderForChannel(nsIChannel* aChannel);
+
+  /**
    * Returns whether the given URI is in the image cache.
    */
-  static bool IsImageInCache(nsIURI* aURI);
+  static bool IsImageInCache(nsIURI* aURI, nsIDocument* aDocument);
 
   /**
    * Method to get an imgIContainer from an image loading content
    *
    * @param aContent The image loading content.  Must not be null.
    * @param aRequest The image request [out]
    * @return the imgIContainer corresponding to the first frame of the image
    */
@@ -2155,19 +2155,21 @@ private:
 
 #ifdef MOZ_XTF
   static nsIXTFService *sXTFService;
 #endif
 
   static bool sImgLoaderInitialized;
   static void InitImgLoader();
 
-  // The following two members are initialized lazily
+  // The following four members are initialized lazily
   static imgILoader* sImgLoader;
+  static imgILoader* sPrivateImgLoader;
   static imgICache* sImgCache;
+  static imgICache* sPrivateImgCache;
 
   static nsIConsoleService* sConsoleService;
 
   static nsDataHashtable<nsISupportsHashKey, EventNameMapping>* sAtomEventTable;
   static nsDataHashtable<nsStringHashKey, EventNameMapping>* sStringEventTable;
   static nsCOMArray<nsIAtom>* sUserDefinedEvents;
 
   static nsIStringBundleService* sStringBundleService;
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -45,16 +45,17 @@ EXPORTS		= \
 
 EXPORTS_NAMESPACES = mozilla/dom
 
 EXPORTS_mozilla/dom = \
   Link.h \
   $(NULL)
 
 LOCAL_INCLUDES = \
+		-I$(topsrcdir)/image/src \
 		$(NULL)
 
 CPPSRCS		= \
 		DirectionalityUtils.cpp \
 		nsAtomListUtils.cpp \
 		nsAttrAndChildArray.cpp \
 		nsAttrValue.cpp \
 		nsAttrValueOrString.cpp \
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -114,31 +114,33 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_
 #include "nsTextEditorState.h"
 #include "nsIPluginHost.h"
 #include "nsICategoryManager.h"
 #include "nsIViewManager.h"
 #include "nsEventStateManager.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsParserConstants.h"
 #include "nsIWebNavigation.h"
+#include "nsILoadContext.h"
 #include "nsTextFragment.h"
 #include "mozilla/Selection.h"
 
 #ifdef IBMBIDI
 #include "nsIBidiKeyboard.h"
 #endif
 #include "nsCycleCollectionParticipant.h"
 
 // for ReportToConsole
 #include "nsIStringBundle.h"
 #include "nsIScriptError.h"
 #include "nsIConsoleService.h"
 
 #include "mozAutoDocUpdate.h"
 #include "imgICache.h"
+#include "imgLoader.h"
 #include "xpcprivate.h" // nsXPConnect
 #include "nsScriptSecurityManager.h"
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsContentDLF.h"
 #ifdef MOZ_MEDIA
 #include "nsHTMLMediaElement.h"
@@ -182,17 +184,19 @@ nsIScriptSecurityManager *nsContentUtils
 nsIThreadJSContextStack *nsContentUtils::sThreadJSContextStack;
 nsIParserService *nsContentUtils::sParserService = nullptr;
 nsINameSpaceManager *nsContentUtils::sNameSpaceManager;
 nsIIOService *nsContentUtils::sIOService;
 #ifdef MOZ_XTF
 nsIXTFService *nsContentUtils::sXTFService = nullptr;
 #endif
 imgILoader *nsContentUtils::sImgLoader;
+imgILoader *nsContentUtils::sPrivateImgLoader;
 imgICache *nsContentUtils::sImgCache;
+imgICache *nsContentUtils::sPrivateImgCache;
 nsIConsoleService *nsContentUtils::sConsoleService;
 nsDataHashtable<nsISupportsHashKey, EventNameMapping>* nsContentUtils::sAtomEventTable = nullptr;
 nsDataHashtable<nsStringHashKey, EventNameMapping>* nsContentUtils::sStringEventTable = nullptr;
 nsCOMArray<nsIAtom>* nsContentUtils::sUserDefinedEvents = nullptr;
 nsIStringBundleService *nsContentUtils::sStringBundleService;
 nsIStringBundle *nsContentUtils::sStringBundles[PropertiesFile_COUNT];
 nsIContentPolicy *nsContentUtils::sContentPolicyService;
 bool nsContentUtils::sTriedToGetContentPolicy = false;
@@ -503,25 +507,27 @@ nsContentUtils::InitializeModifierString
 bool nsContentUtils::sImgLoaderInitialized;
 
 void
 nsContentUtils::InitImgLoader()
 {
   sImgLoaderInitialized = true;
 
   // Ignore failure and just don't load images
-  nsresult rv = CallGetService("@mozilla.org/image/loader;1", &sImgLoader);
-  if (NS_FAILED(rv)) {
-    // no image loading for us.  Oh, well.
-    sImgLoader = nullptr;
-    sImgCache = nullptr;
-  } else {
-    if (NS_FAILED(CallGetService("@mozilla.org/image/cache;1", &sImgCache )))
-      sImgCache = nullptr;
-  }
+  nsresult rv = CallCreateInstance("@mozilla.org/image/loader;1", &sImgLoader);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "Creation should have succeeded");
+  rv = CallCreateInstance("@mozilla.org/image/loader;1", &sPrivateImgLoader);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "Creation should have succeeded");
+
+  rv = CallQueryInterface(sImgLoader, &sImgCache);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "imgICache and imgILoader should be paired");
+  rv = CallQueryInterface(sPrivateImgLoader, &sPrivateImgCache);
+  NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "imgICache and imgILoader should be paired");
+
+  sPrivateImgCache->RespectPrivacyNotifications();
 }
 
 bool
 nsContentUtils::InitializeEventTable() {
   NS_ASSERTION(!sAtomEventTable, "EventTable already initialized!");
   NS_ASSERTION(!sStringEventTable, "EventTable already initialized!");
 
   static const EventNameMapping eventArray[] = {
@@ -1488,17 +1494,19 @@ nsContentUtils::Shutdown()
   NS_IF_RELEASE(sParserService);
   NS_IF_RELEASE(sIOService);
   NS_IF_RELEASE(sLineBreaker);
   NS_IF_RELEASE(sWordBreaker);
 #ifdef MOZ_XTF
   NS_IF_RELEASE(sXTFService);
 #endif
   NS_IF_RELEASE(sImgLoader);
+  NS_IF_RELEASE(sPrivateImgLoader);
   NS_IF_RELEASE(sImgCache);
+  NS_IF_RELEASE(sPrivateImgCache);
 #ifdef IBMBIDI
   NS_IF_RELEASE(sBidiKeyboard);
 #endif
 
   delete sAtomEventTable;
   sAtomEventTable = nullptr;
   delete sStringEventTable;
   sStringEventTable = nullptr;
@@ -2687,45 +2695,86 @@ nsContentUtils::CanLoadImage(nsIURI* aUR
 
   if (aImageBlockingStatus) {
     *aImageBlockingStatus =
       NS_FAILED(rv) ? nsIContentPolicy::REJECT_REQUEST : decision;
   }
   return NS_FAILED(rv) ? false : NS_CP_ACCEPTED(decision);
 }
 
+imgILoader*
+nsContentUtils::GetImgLoaderForDocument(nsIDocument* aDoc)
+{
+  if (!sImgLoaderInitialized)
+    InitImgLoader();
+  if (!aDoc)
+    return sImgLoader;
+  bool isPrivate = false;
+  nsCOMPtr<nsILoadGroup> loadGroup = aDoc->GetDocumentLoadGroup();
+  nsCOMPtr<nsIInterfaceRequestor> callbacks;
+  if (loadGroup) {
+    loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+    if (callbacks) {
+      nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+      isPrivate = loadContext && loadContext->UsePrivateBrowsing();
+    }
+  } else {
+    nsCOMPtr<nsIChannel> channel = aDoc->GetChannel();
+    if (channel) {
+      nsCOMPtr<nsILoadContext> context;
+      NS_QueryNotificationCallbacks(channel, context);
+      isPrivate = context && context->UsePrivateBrowsing();
+    }
+  }
+  return isPrivate ? sPrivateImgLoader : sImgLoader;
+}
+
+// static
+imgILoader*
+nsContentUtils::GetImgLoaderForChannel(nsIChannel* aChannel)
+{
+  if (!sImgLoaderInitialized)
+    InitImgLoader();
+  if (!aChannel)
+    return sImgLoader;
+  nsCOMPtr<nsILoadContext> context;
+  NS_QueryNotificationCallbacks(aChannel, context);
+  return context && context->UsePrivateBrowsing() ? sPrivateImgLoader : sImgLoader;
+}
+
 // static
 bool
-nsContentUtils::IsImageInCache(nsIURI* aURI)
+nsContentUtils::IsImageInCache(nsIURI* aURI, nsIDocument* aDocument)
 {
     if (!sImgLoaderInitialized)
         InitImgLoader();
 
-    if (!sImgCache) return false;
+    imgILoader* loader = GetImgLoaderForDocument(aDocument);
+    nsCOMPtr<imgICache> cache = do_QueryInterface(loader);
 
     // If something unexpected happened we return false, otherwise if props
     // is set, the image is cached and we return true
     nsCOMPtr<nsIProperties> props;
-    nsresult rv = sImgCache->FindEntryProperties(aURI, getter_AddRefs(props));
+    nsresult rv = cache->FindEntryProperties(aURI, getter_AddRefs(props));
     return (NS_SUCCEEDED(rv) && props);
 }
 
 // static
 nsresult
 nsContentUtils::LoadImage(nsIURI* aURI, nsIDocument* aLoadingDocument,
                           nsIPrincipal* aLoadingPrincipal, nsIURI* aReferrer,
                           imgIDecoderObserver* aObserver, int32_t aLoadFlags,
                           imgIRequest** aRequest)
 {
   NS_PRECONDITION(aURI, "Must have a URI");
   NS_PRECONDITION(aLoadingDocument, "Must have a document");
   NS_PRECONDITION(aLoadingPrincipal, "Must have a principal");
   NS_PRECONDITION(aRequest, "Null out param");
 
-  imgILoader* imgLoader = GetImgLoader();
+  imgILoader* imgLoader = GetImgLoaderForDocument(aLoadingDocument);
   if (!imgLoader) {
     // nothing we can do here
     return NS_OK;
   }
 
   nsCOMPtr<nsILoadGroup> loadGroup = aLoadingDocument->GetDocumentLoadGroup();
   NS_ASSERTION(loadGroup, "Could not get loadgroup; onload may fire too early");
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -7676,17 +7676,17 @@ FireOrClearDelayedEvents(nsTArray<nsCOMP
 
 void
 nsDocument::MaybePreLoadImage(nsIURI* uri, const nsAString &aCrossOriginAttr)
 {
   // Early exit if the img is already present in the img-cache
   // which indicates that the "real" load has already started and
   // that we shouldn't preload it.
   int16_t blockingStatus;
-  if (nsContentUtils::IsImageInCache(uri) ||
+  if (nsContentUtils::IsImageInCache(uri, static_cast<nsIDocument *>(this)) ||
       !nsContentUtils::CanLoadImage(uri, static_cast<nsIDocument *>(this),
                                     this, NodePrincipal(), &blockingStatus)) {
     return;
   }
 
   nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
   switch (nsGenericElement::StringToCORSMode(aCrossOriginAttr)) {
   case CORS_NONE:
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -83,17 +83,17 @@ nsImageLoadingContent::nsImageLoadingCon
     mBroken(true),
     mUserDisabled(false),
     mSuppressed(false),
     mNewRequestsWillNeedAnimationReset(false),
     mStateChangerDepth(0),
     mCurrentRequestRegistered(false),
     mPendingRequestRegistered(false)
 {
-  if (!nsContentUtils::GetImgLoader()) {
+  if (!nsContentUtils::GetImgLoaderForChannel(nullptr)) {
     mLoadingEnabled = false;
   }
 }
 
 void
 nsImageLoadingContent::DestroyImageLoadingContent()
 {
   // Cancel our requests so they won't hold stale refs to us
@@ -329,17 +329,17 @@ nsImageLoadingContent::GetLoadingEnabled
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
-  if (nsContentUtils::GetImgLoader()) {
+  if (nsContentUtils::GetImgLoaderForChannel(nullptr)) {
     mLoadingEnabled = aLoadingEnabled;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsImageLoadingContent::GetImageBlockingStatus(int16_t* aStatus)
 {
@@ -513,17 +513,17 @@ nsImageLoadingContent::GetCurrentURI(nsI
 }
 
 NS_IMETHODIMP
 nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
                                             nsIStreamListener** aListener)
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
-  if (!nsContentUtils::GetImgLoader()) {
+  if (!nsContentUtils::GetImgLoaderForChannel(aChannel)) {
     return NS_ERROR_NULL_POINTER;
   }
 
   nsCOMPtr<nsIDocument> doc = GetOurOwnerDoc();
   if (!doc) {
     // Don't bother
     return NS_OK;
   }
@@ -532,17 +532,17 @@ nsImageLoadingContent::LoadImageWithChan
   // Shouldn't that be done before the start of the load?
   // XXX what about shouldProcess?
 
   // Our state might change. Watch it.
   AutoStateChanger changer(this, true);
 
   // Do the load.
   nsCOMPtr<imgIRequest>& req = PrepareNextRequest();
-  nsresult rv = nsContentUtils::GetImgLoader()->
+  nsresult rv = nsContentUtils::GetImgLoaderForChannel(aChannel)->
     LoadImageWithChannel(aChannel, this, doc, aListener,
                          getter_AddRefs(req));
   if (NS_SUCCEEDED(rv)) {
     TrackImage(req);
     ResetAnimationIfNeeded();
   } else {
     // If we don't have a current URI, we might as well store this URI so people
     // know what we tried (and failed) to load.
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -472,17 +472,17 @@ URIEquals(nsIURI *a, nsIURI *b)
 {
   bool equal;
   return (!a && !b) || (a && b && NS_SUCCEEDED(a->Equals(b, &equal)) && equal);
 }
 
 static bool
 IsSupportedImage(const nsCString& aMimeType)
 {
-  imgILoader* loader = nsContentUtils::GetImgLoader();
+  nsCOMPtr<imgILoader> loader = nsContentUtils::GetImgLoaderForChannel(nullptr);
   if (!loader) {
     return false;
   }
 
   bool supported;
   nsresult rv = loader->SupportImageWithMimeType(aMimeType.get(), &supported);
   return NS_SUCCEEDED(rv) && supported;
 }
--- a/docshell/base/nsWebNavigationInfo.cpp
+++ b/docshell/base/nsWebNavigationInfo.cpp
@@ -19,19 +19,19 @@ NS_IMPL_ISUPPORTS1(nsWebNavigationInfo, 
 
 nsresult
 nsWebNavigationInfo::Init()
 {
   nsresult rv;
   mCategoryManager = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mImgLoader = do_GetService("@mozilla.org/image/loader;1", &rv);
+  mImgLoader = nsContentUtils::GetImgLoaderForChannel(nullptr);
 
-  return rv;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
                                      nsIWebNavigation* aWebNav,
                                      uint32_t* aIsTypeSupported)
 {
   NS_PRECONDITION(aIsTypeSupported, "null out param?");
--- a/embedding/browser/webBrowser/nsContextMenuInfo.cpp
+++ b/embedding/browser/webBrowser/nsContextMenuInfo.cpp
@@ -296,18 +296,17 @@ nsContextMenuInfo::GetBackgroundImageReq
       primitiveValue = do_QueryInterface(cssValue);
       if (primitiveValue) {
         primitiveValue->GetStringValue(bgStringValue);
         if (!bgStringValue.EqualsLiteral("none")) {
           nsCOMPtr<nsIURI> bgUri;
           NS_NewURI(getter_AddRefs(bgUri), bgStringValue);
           NS_ENSURE_TRUE(bgUri, NS_ERROR_FAILURE);
 
-          nsCOMPtr<imgILoader> il(do_GetService(
-                                    "@mozilla.org/image/loader;1"));
+          nsCOMPtr<imgILoader> il(do_CreateInstance("@mozilla.org/image/loader;1"));
           NS_ENSURE_TRUE(il, NS_ERROR_FAILURE);
 
           return il->LoadImage(bgUri, nullptr, nullptr, principal, nullptr,
                                nullptr, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
                                nullptr, channelPolicy, aRequest);
         }
       }
 
--- a/image/build/nsImageModule.cpp
+++ b/image/build/nsImageModule.cpp
@@ -91,17 +91,17 @@ static const mozilla::Module::CategoryEn
   { "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
   { NULL }
 };
 
 static nsresult
 imglib_Initialize()
 {
   mozilla::image::DiscardTracker::Initialize();
-  imgLoader::InitCache();
+  imgLoader::GlobalInit();
   return NS_OK;
 }
 
 static void
 imglib_Shutdown()
 {
   imgLoader::Shutdown();
   mozilla::image::DiscardTracker::Shutdown();
--- a/image/public/imgICache.idl
+++ b/image/public/imgICache.idl
@@ -12,17 +12,17 @@ interface nsIProperties;
 
 /**
  * imgICache interface
  *
  * @author Stuart Parmenter <pavlov@netscape.com>
  * @version 0.1
  * @see imagelib2
  */
-[scriptable, uuid(f1b74aae-5661-4753-a21c-66dd644afebc)]
+[scriptable, uuid(b06e0fa5-d6e2-4fa3-8fc0-7775aed96522)]
 interface imgICache : nsISupports
 {
   /**
    * Evict images from the cache.
    *
    * @param chrome If TRUE,  evict only chrome images.
    *               If FALSE, evict everything except chrome images.
    */
@@ -44,9 +44,16 @@ interface imgICache : nsISupports
    * 'content-disposition' will be a nsISupportsCString containing the header
    * If you call this before any data has been loaded from a URI, it will succeed,
    * but come back empty.
    *
    * @param uri The URI to look up.
    * @returns NULL if the URL was not found in the cache
    */
   nsIProperties findEntryProperties(in nsIURI uri);
+
+  /**
+   * Make this cache instance respect private browsing notifications. This entails clearing
+   * the chrome and content caches whenever the last-pb-context-exited notification is
+   * observed.
+   */
+  void respectPrivacyNotifications();
 };
--- a/image/public/imgITools.idl
+++ b/image/public/imgITools.idl
@@ -3,18 +3,21 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIInputStream;
 interface imgIContainer;
+interface imgILoader;
+interface imgICache;
+interface nsIDOMDocument;
 
-[scriptable, uuid(8e16f39e-7012-46bd-aa22-2a7a3265608f)]
+[scriptable, uuid(53dd1cbe-cb9f-4d9e-8104-1ab72851c88e)]
 interface imgITools : nsISupports
 {
     /**
      * decodeImageData
      * Caller provides an input stream and mimetype. We read from the stream
      * and decompress it (according to the specified mime type) and return
      * the resulting imgIContainer. (If the caller already has a container,
      * it can be provided as input to be reused).
@@ -67,16 +70,36 @@ interface imgITools : nsISupports
      */
     nsIInputStream encodeScaledImage(in imgIContainer aContainer,
                                      in ACString aMimeType,
                                      in long aWidth,
                                      in long aHeight,
                                      [optional] in AString outputOptions);
 
     /**
+     * getImgLoaderForDocument
+     * Retrieve an image loader that reflects the privacy status of the given
+     * document.
+     *
+     * @param doc
+     *        A document. Must not be null.
+     */
+    imgILoader getImgLoaderForDocument(in nsIDOMDocument doc);
+
+    /**
+     * getImgLoaderForDocument
+     * Retrieve an image cache that reflects the privacy status of the given
+     * document.
+     *
+     * @param doc
+     *        A document. Must not be null.
+     */
+    imgICache getImgCacheForDocument(in nsIDOMDocument doc);
+
+    /**
      * encodeCroppedImage
      * Caller provides an image container, and the mime type it should be
      * encoded to. We return an input stream for the encoded image data.
      * The encoded image is cropped to the specified dimensions.
      *
      * The given offset and size must not exceed the image bounds.
      *
      * @param aContainer
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -45,24 +45,24 @@
 #include "nsURILoader.h"
 
 #include "nsIComponentRegistrar.h"
 
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 
 #include "nsIMemoryReporter.h"
-#include "nsIPrivateBrowsingService.h"
 
 // we want to explore making the document own the load group
 // so we can associate the document URI with the load group.
 // until this point, we have an evil hack:
 #include "nsIHttpChannelInternal.h"  
 #include "nsIContentSecurityPolicy.h"
 #include "nsIChannelPolicy.h"
+#include "nsILoadContext.h"
 
 #include "nsContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::image;
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(ImagesMallocSizeOf, "images")
 
@@ -82,18 +82,21 @@ public:
     return NS_OK;
   }
 
   NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *callback,
                             nsISupports *closure)
   {
     AllSizes chrome;
     AllSizes content;
-    imgLoader::sChromeCache.EnumerateRead(EntryAllSizes, &chrome);
-    imgLoader::sCache.EnumerateRead(EntryAllSizes, &content);
+
+    for (PRUint32 i = 0; i < mKnownLoaders.Length(); i++) {
+      mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryAllSizes, &chrome);
+      mKnownLoaders[i]->mCache.EnumerateRead(EntryAllSizes, &content);
+    }
 
 #define REPORT(_path, _kind, _amount, _desc)                                  \
     do {                                                                      \
       nsresult rv;                                                            \
       rv = callback->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path),      \
                               _kind, nsIMemoryReporter::UNITS_BYTES, _amount, \
                               NS_LITERAL_CSTRING(_desc), closure);            \
       NS_ENSURE_SUCCESS(rv, rv);                                              \
@@ -150,30 +153,46 @@ public:
 #undef REPORT
 
     return NS_OK;
   }
 
   NS_IMETHOD GetExplicitNonHeap(int64_t *n)
   {
     size_t n2 = 0;
-    imgLoader::sChromeCache.EnumerateRead(EntryExplicitNonHeapSize, &n2);
-    imgLoader::sCache.EnumerateRead(EntryExplicitNonHeapSize, &n2);
+    for (PRUint32 i = 0; i < mKnownLoaders.Length(); i++) {
+      mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryExplicitNonHeapSize, &n2);
+      mKnownLoaders[i]->mCache.EnumerateRead(EntryExplicitNonHeapSize, &n2);
+    }
     *n = n2;
     return NS_OK;
   }
 
   static int64_t GetImagesContentUsedUncompressed()
   {
     size_t n = 0;
-    imgLoader::sCache.EnumerateRead(EntryUsedUncompressedSize, &n);
+    for (PRUint32 i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length(); i++) {
+      imgLoader::sMemReporter->mKnownLoaders[i]->mCache.EnumerateRead(EntryUsedUncompressedSize, &n);
+    }
     return n;
   }
 
+  void RegisterLoader(imgLoader* aLoader)
+  {
+    mKnownLoaders.AppendElement(aLoader);
+  }
+
+  void UnregisterLoader(imgLoader* aLoader)
+  {
+    mKnownLoaders.RemoveElement(aLoader);
+  }
+
 private:
+  nsTArray<imgLoader*> mKnownLoaders;
+
   struct AllSizes {
     size_t mUsedRaw;
     size_t mUsedUncompressedHeap;
     size_t mUsedUncompressedNonheap;
     size_t mUnusedRaw;
     size_t mUnusedUncompressedHeap;
     size_t mUnusedUncompressedNonheap;
 
@@ -334,21 +353,21 @@ nsProgressNotificationProxy::GetInterfac
     NS_ADDREF_THIS();
     return NS_OK;
   }
   if (mOriginalCallbacks)
     return mOriginalCallbacks->GetInterface(iid, result);
   return NS_NOINTERFACE;
 }
 
-static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry,
+static void NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader,
                                imgRequest **aRequest, imgCacheEntry **aEntry)
 {
-  nsRefPtr<imgRequest> request = new imgRequest();
-  nsRefPtr<imgCacheEntry> entry = new imgCacheEntry(request, aForcePrincipalCheckForCacheEntry);
+  nsRefPtr<imgRequest> request = new imgRequest(aLoader);
+  nsRefPtr<imgCacheEntry> entry = new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
   request.forget(aRequest);
   entry.forget(aEntry);
 }
 
 static bool ShouldRevalidateEntry(imgCacheEntry *aEntry,
                               nsLoadFlags aFlags,
                               bool aHasExpired)
 {
@@ -506,18 +525,19 @@ static nsresult NewImageChannel(nsIChann
   return NS_OK;
 }
 
 static uint32_t SecondsFromPRTime(PRTime prTime)
 {
   return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
 }
 
-imgCacheEntry::imgCacheEntry(imgRequest *request, bool forcePrincipalCheck)
- : mRequest(request),
+imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest *request, bool forcePrincipalCheck)
+ : mLoader(loader),
+   mRequest(request),
    mDataSize(0),
    mTouchedTime(SecondsFromPRTime(PR_Now())),
    mExpiryTime(0),
    mMustValidate(false),
    // We start off as evicted so we don't try to update the cache. PutIntoCache
    // will set this to false.
    mEvicted(true),
    mHasNoProxies(true),
@@ -541,17 +561,17 @@ void imgCacheEntry::Touch(bool updateTim
 
 void imgCacheEntry::UpdateCache(int32_t diff /* = 0 */)
 {
   // Don't update the cache if we've been removed from it or it doesn't care
   // about our size or usage.
   if (!Evicted() && HasNoProxies()) {
     nsCOMPtr<nsIURI> uri;
     mRequest->GetURI(getter_AddRefs(uri));
-    imgLoader::CacheEntriesChanged(uri, diff);
+    mLoader->CacheEntriesChanged(uri, diff);
   }
 }
 
 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies)
 {
 #if defined(PR_LOGGING)
   nsCOMPtr<nsIURI> uri;
   mRequest->GetURI(getter_AddRefs(uri));
@@ -701,31 +721,25 @@ nsresult imgLoader::CreateNewProxyForReq
   return NS_OK;
 }
 
 class imgCacheObserver MOZ_FINAL : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
-private:
-  imgLoader mLoader;
 };
 
 NS_IMPL_ISUPPORTS1(imgCacheObserver, nsIObserver)
 
 NS_IMETHODIMP
 imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aSomeData)
 {
   if (strcmp(aTopic, "memory-pressure") == 0) {
     DiscardTracker::DiscardAll();
-    mLoader.MinimizeCaches();
-  } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
-             strcmp(aTopic, "chrome-flush-caches") == 0) {
-    mLoader.ClearChromeImageCache();
   }
   return NS_OK;
 }
 
 class imgCacheExpirationTracker MOZ_FINAL
   : public nsExpirationTracker<imgCacheEntry, 3>
 {
   enum { TIMEOUT_SECONDS = 10 };
@@ -755,150 +769,174 @@ void imgCacheExpirationTracker::NotifyEx
     uri->GetSpec(spec);
     LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheExpirationTracker::NotifyExpired", "entry", spec.get());
   }
 #endif
 
   // We can be called multiple times on the same entry. Don't do work multiple
   // times.
   if (!entry->Evicted())
-    imgLoader::RemoveFromCache(entry);
+    entry->Loader()->RemoveFromCache(entry);
 
-  imgLoader::VerifyCacheSizes();
+  entry->Loader()->VerifyCacheSizes();
 }
 
 imgCacheObserver *gCacheObserver;
-imgCacheExpirationTracker *gCacheTracker;
-
-imgLoader::imgCacheTable imgLoader::sCache;
-imgCacheQueue imgLoader::sCacheQueue;
-
-imgLoader::imgCacheTable imgLoader::sChromeCache;
-imgCacheQueue imgLoader::sChromeCacheQueue;
 
 double imgLoader::sCacheTimeWeight;
 uint32_t imgLoader::sCacheMaxSize;
+imgMemoryReporter* imgLoader::sMemReporter;
 
 NS_IMPL_ISUPPORTS5(imgLoader, imgILoader, nsIContentSniffer, imgICache, nsISupportsWeakReference, nsIObserver)
 
 imgLoader::imgLoader()
+: mRespectPrivacy(false)
 {
-  /* member initializers and constructor code */
+  sMemReporter->AddRef();
+  sMemReporter->RegisterLoader(this);
 }
 
 imgLoader::~imgLoader()
 {
-  /* destructor code */
+  ClearChromeImageCache();
+  ClearImageCache();
+  sMemReporter->UnregisterLoader(this);
+  sMemReporter->Release();
 }
 
 void imgLoader::VerifyCacheSizes()
 {
 #ifdef DEBUG
-  if (!gCacheTracker)
+  if (!mCacheTracker)
     return;
 
-  uint32_t cachesize = sCache.Count() + sChromeCache.Count();
-  uint32_t queuesize = sCacheQueue.GetNumElements() + sChromeCacheQueue.GetNumElements();
+  uint32_t cachesize = mCache.Count() + mChromeCache.Count();
+  uint32_t queuesize = mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
   uint32_t trackersize = 0;
-  for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(gCacheTracker); it.Next(); )
+  for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker); it.Next(); )
     trackersize++;
   NS_ABORT_IF_FALSE(queuesize == trackersize, "Queue and tracker sizes out of sync!");
   NS_ABORT_IF_FALSE(queuesize <= cachesize, "Queue has more elements than cache!");
 #endif
 }
 
 imgLoader::imgCacheTable & imgLoader::GetCache(nsIURI *aURI)
 {
   bool chrome = false;
   aURI->SchemeIs("chrome", &chrome);
   if (chrome)
-    return sChromeCache;
+    return mChromeCache;
   else
-    return sCache;
+    return mCache;
 }
 
 imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI)
 {
   bool chrome = false;
   aURI->SchemeIs("chrome", &chrome);
   if (chrome)
-    return sChromeCacheQueue;
+    return mChromeCacheQueue;
   else
-    return sCacheQueue;
+    return mCacheQueue;
 }
 
-nsresult imgLoader::InitCache()
+void imgLoader::GlobalInit()
 {
-  NS_TIME_FUNCTION;
-
-  nsresult rv;
-  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-  if (!os)
-    return NS_ERROR_FAILURE;
-  
   gCacheObserver = new imgCacheObserver();
   NS_ADDREF(gCacheObserver);
 
-  os->AddObserver(gCacheObserver, "memory-pressure", false);
-  os->AddObserver(gCacheObserver, "chrome-flush-skin-caches", false);
-  os->AddObserver(gCacheObserver, "chrome-flush-caches", false);
-
-  gCacheTracker = new imgCacheExpirationTracker();
-
-  sCache.Init();
-  sChromeCache.Init();
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (os)
+    os->AddObserver(gCacheObserver, "memory-pressure", false);
 
   int32_t timeweight;
-  rv = Preferences::GetInt("image.cache.timeweight", &timeweight);
+  nsresult rv = Preferences::GetInt("image.cache.timeweight", &timeweight);
   if (NS_SUCCEEDED(rv))
     sCacheTimeWeight = timeweight / 1000.0;
   else
     sCacheTimeWeight = 0.5;
 
   int32_t cachesize;
   rv = Preferences::GetInt("image.cache.size", &cachesize);
   if (NS_SUCCEEDED(rv))
     sCacheMaxSize = cachesize;
   else
     sCacheMaxSize = 5 * 1024 * 1024;
 
-  NS_RegisterMemoryMultiReporter(new imgMemoryReporter());
+  sMemReporter = new imgMemoryReporter();
+  NS_RegisterMemoryMultiReporter(sMemReporter);
   NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(ImagesContentUsedUncompressed));
   
   return NS_OK;
 }
 
+nsresult imgLoader::InitCache()
+{
+  NS_TIME_FUNCTION;
+
+  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+  if (!os)
+    return NS_ERROR_FAILURE;
+
+  os->AddObserver(this, "memory-pressure", false);
+  os->AddObserver(this, "chrome-flush-skin-caches", false);
+  os->AddObserver(this, "chrome-flush-caches", false);
+  os->AddObserver(this, "last-pb-context-exited", false);
+  os->AddObserver(this, "profile-before-change", false);
+  os->AddObserver(this, "xpcom-shutdown", false);
+
+  mCacheTracker = new imgCacheExpirationTracker();
+
+  mCache.Init();
+  mChromeCache.Init();
+
+    return NS_OK;
+}
+
 nsresult imgLoader::Init()
 {
+  InitCache();
+
   ReadAcceptHeaderPref();
 
   Preferences::AddWeakObserver(this, "image.http.accept");
 
-  // Listen for when we leave private browsing mode
-  nsCOMPtr<nsIObserverService> obService = mozilla::services::GetObserverService();
-  if (obService)
-    obService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, true);
+    return NS_OK;
+}
 
+NS_IMETHODIMP
+imgLoader::RespectPrivacyNotifications()
+{
+  mRespectPrivacy = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 imgLoader::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData)
 {
   // We listen for pref change notifications...
   if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
     if (!strcmp(NS_ConvertUTF16toUTF8(aData).get(), "image.http.accept")) {
       ReadAcceptHeaderPref();
     }
-  }
 
-  // ...and exits from private browsing.
-  else if (!strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC)) {
-    if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData))
+  } else if (strcmp(aTopic, "memory-pressure") == 0) {
+    MinimizeCaches();
+  } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
+             strcmp(aTopic, "chrome-flush-caches") == 0) {
+    MinimizeCaches();
+    ClearChromeImageCache();
+  } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
+    if (mRespectPrivacy) {
       ClearImageCache();
+      ClearChromeImageCache();
+    }
+  } else if (strcmp(aTopic, "profile-before-change") == 0 ||
+             strcmp(aTopic, "xpcom-shutdown") == 0) {
+    mCacheTracker = nullptr;
   }
 
   // (Nothing else should bring us here)
   else {
     NS_ABORT_IF_FALSE(0, "Invalid topic received");
   }
 
   return NS_OK;
@@ -937,52 +975,48 @@ NS_IMETHODIMP imgLoader::FindEntryProper
   nsRefPtr<imgCacheEntry> entry;
   nsCAutoString spec;
   imgCacheTable &cache = GetCache(uri);
 
   uri->GetSpec(spec);
   *_retval = nullptr;
 
   if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
-    if (gCacheTracker && entry->HasNoProxies())
-      gCacheTracker->MarkUsed(entry);
+    if (mCacheTracker && entry->HasNoProxies())
+      mCacheTracker->MarkUsed(entry);
 
     nsRefPtr<imgRequest> request = getter_AddRefs(entry->GetRequest());
     if (request) {
       *_retval = request->Properties();
       NS_ADDREF(*_retval);
     }
   }
 
   return NS_OK;
 }
 
 void imgLoader::Shutdown()
 {
-  ClearChromeImageCache();
-  ClearImageCache();
-  NS_IF_RELEASE(gCacheObserver);
-  delete gCacheTracker;
-  gCacheTracker = nullptr;
+  NS_RELEASE(gCacheObserver);
 }
 
 nsresult imgLoader::ClearChromeImageCache()
 {
-  return EvictEntries(sChromeCache);
+  return EvictEntries(mChromeCache);
 }
 
 nsresult imgLoader::ClearImageCache()
 {
-  return EvictEntries(sCache);
+  return EvictEntries(mCache);
 }
 
 void imgLoader::MinimizeCaches()
 {
-  EvictEntries(sCacheQueue);
-  EvictEntries(sChromeCacheQueue);
+  EvictEntries(mCacheQueue);
+  EvictEntries(mChromeCacheQueue);
 }
 
 bool imgLoader::PutIntoCache(nsIURI *key, imgCacheEntry *entry)
 {
   imgCacheTable &cache = GetCache(key);
 
   nsCAutoString spec;
   key->GetSpec(spec);
@@ -1015,18 +1049,18 @@ bool imgLoader::PutIntoCache(nsIURI *key
   if (entry->Evicted())
     entry->SetEvicted(false);
 
   // If we're resurrecting an entry with no proxies, put it back in the
   // tracker and queue.
   if (entry->HasNoProxies()) {
     nsresult addrv = NS_OK;
 
-    if (gCacheTracker)
-      addrv = gCacheTracker->AddObject(entry);
+    if (mCacheTracker)
+      addrv = mCacheTracker->AddObject(entry);
 
     if (NS_SUCCEEDED(addrv)) {
       imgCacheQueue &queue = GetCacheQueue(key);
       queue.Push(entry);
     }
   }
 
   nsRefPtr<imgRequest> request(getter_AddRefs(entry->GetRequest()));
@@ -1046,18 +1080,18 @@ bool imgLoader::SetHasNoProxies(nsIURI *
 
   if (entry->Evicted())
     return false;
 
   imgCacheQueue &queue = GetCacheQueue(key);
 
   nsresult addrv = NS_OK;
 
-  if (gCacheTracker)
-    addrv = gCacheTracker->AddObject(entry);
+  if (mCacheTracker)
+    addrv = mCacheTracker->AddObject(entry);
 
   if (NS_SUCCEEDED(addrv)) {
     queue.Push(entry);
     entry->SetHasNoProxies(true);
   }
 
   imgCacheTable &cache = GetCache(key);
   CheckCacheLimits(cache, queue);
@@ -1076,18 +1110,18 @@ bool imgLoader::SetHasProxies(nsIURI *ke
 
   LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::SetHasProxies", "uri", spec.get());
 
   nsRefPtr<imgCacheEntry> entry;
   if (cache.Get(spec, getter_AddRefs(entry)) && entry && entry->HasNoProxies()) {
     imgCacheQueue &queue = GetCacheQueue(key);
     queue.Remove(entry);
 
-    if (gCacheTracker)
-      gCacheTracker->RemoveObject(entry);
+    if (mCacheTracker)
+      mCacheTracker->RemoveObject(entry);
 
     entry->SetHasNoProxies(false);
 
     return true;
   }
 
   return false;
 }
@@ -1202,17 +1236,17 @@ bool imgLoader::ValidateRequestWithNewCh
 
     // Make sure that OnStatus/OnProgress calls have the right request set...
     nsRefPtr<nsProgressNotificationProxy> progressproxy =
         new nsProgressNotificationProxy(newChannel, req);
     if (!progressproxy)
       return false;
 
     nsRefPtr<imgCacheValidator> hvc =
-      new imgCacheValidator(progressproxy, request, aCX, forcePrincipalCheck);
+      new imgCacheValidator(progressproxy, this, request, aCX, forcePrincipalCheck);
 
     nsCOMPtr<nsIStreamListener> listener = hvc.get();
 
     // We must set the notification callbacks before setting up the
     // CORS listener, because that's also interested inthe
     // notification callbacks.
     newChannel->SetNotificationCallbacks(hvc);
 
@@ -1383,18 +1417,18 @@ bool imgLoader::RemoveFromCache(nsIURI *
   nsRefPtr<imgCacheEntry> entry;
   if (cache.Get(spec, getter_AddRefs(entry)) && entry) {
     cache.Remove(spec);
 
     NS_ABORT_IF_FALSE(!entry->Evicted(), "Evicting an already-evicted cache entry!");
 
     // Entries with no proxies are in the tracker.
     if (entry->HasNoProxies()) {
-      if (gCacheTracker)
-        gCacheTracker->RemoveObject(entry);
+      if (mCacheTracker)
+        mCacheTracker->RemoveObject(entry);
       queue.Remove(entry);
     }
 
     entry->SetEvicted(true);
 
     nsRefPtr<imgRequest> request(getter_AddRefs(entry->GetRequest()));
     request->SetIsInCache(false);
 
@@ -1418,18 +1452,18 @@ bool imgLoader::RemoveFromCache(imgCache
       key->GetSpec(spec);
 
       LOG_STATIC_FUNC_WITH_PARAM(gImgLog, "imgLoader::RemoveFromCache", "entry's uri", spec.get());
 
       cache.Remove(spec);
 
       if (entry->HasNoProxies()) {
         LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache removing from tracker");
-        if (gCacheTracker)
-          gCacheTracker->RemoveObject(entry);
+        if (mCacheTracker)
+          mCacheTracker->RemoveObject(entry);
         queue.Remove(entry);
       }
 
       entry->SetEvicted(true);
       request->SetIsInCache(false);
 
       return true;
     }
@@ -1519,16 +1553,35 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR
 
   *_retval = nullptr;
 
   nsRefPtr<imgRequest> request;
 
   nsresult rv;
   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
 
+#ifdef DEBUG
+  bool isPrivate = false;
+
+  nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+  if (channel) {
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(channel, loadContext);
+    isPrivate = loadContext && loadContext->UsePrivateBrowsing();
+  } else if (aLoadGroup) {
+    nsCOMPtr<nsIInterfaceRequestor> callbacks;
+    aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+    if (callbacks) {
+      nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
+      isPrivate = loadContext && loadContext->UsePrivateBrowsing();
+    }
+  }
+  MOZ_ASSERT(isPrivate == mRespectPrivacy);
+#endif
+
   // Get the default load flags from the loadgroup (if possible)...
   if (aLoadGroup) {
     aLoadGroup->GetLoadFlags(&requestFlags);
   }
   //
   // Merge the default load flags with those passed in via aLoadFlags.
   // Currently, *only* the caching, validation and background load flags
   // are merged...
@@ -1572,18 +1625,18 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR
       request = getter_AddRefs(entry->GetRequest());
 
       // If this entry has no proxies, its request has no reference to the entry.
       if (entry->HasNoProxies()) {
         LOG_FUNC_WITH_PARAM(gImgLog, "imgLoader::LoadImage() adding proxyless entry", "uri", spec.get());
         NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!");
         request->SetCacheEntry(entry);
 
-        if (gCacheTracker)
-          gCacheTracker->MarkUsed(entry);
+        if (mCacheTracker)
+          mCacheTracker->MarkUsed(entry);
       } 
 
       entry->Touch();
 
 #ifdef DEBUG_joe
       printf("CACHEGET: %d %s %d\n", time(NULL), spec.get(), entry->SizeOfData());
 #endif
     }
@@ -1610,18 +1663,23 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR
                          aLoadGroup,
                          mAcceptHeader,
                          requestFlags,
                          aPolicy,
                          aLoadingPrincipal);
     if (NS_FAILED(rv))
       return NS_ERROR_FAILURE;
 
-    NewRequestAndEntry(forcePrincipalCheck, getter_AddRefs(request),
-                       getter_AddRefs(entry));
+#ifdef DEBUG
+    nsCOMPtr<nsILoadContext> loadContext;
+    NS_QueryNotificationCallbacks(newChannel, loadContext);
+    MOZ_ASSERT_IF(loadContext, loadContext->UsePrivateBrowsing() == mRespectPrivacy);
+#endif
+
+    NewRequestAndEntry(forcePrincipalCheck, this, getter_AddRefs(request), getter_AddRefs(entry));
 
     PR_LOG(gImgLog, PR_LOG_DEBUG,
            ("[this=%p] imgLoader::LoadImage -- Created new imgRequest [request=%p]\n", this, request.get()));
 
     // Create a loadgroup for this new channel.  This way if the channel
     // is redirected, we'll have a way to cancel the resulting channel.
     nsCOMPtr<nsILoadGroup> loadGroup =
         do_CreateInstance(NS_LOADGROUP_CONTRACTID);
@@ -1736,16 +1794,22 @@ NS_IMETHODIMP imgLoader::LoadImage(nsIUR
   return NS_OK;
 }
 
 /* imgIRequest loadImageWithChannel(in nsIChannel channel, in imgIDecoderObserver aObserver, in nsISupports cx, out nsIStreamListener); */
 NS_IMETHODIMP imgLoader::LoadImageWithChannel(nsIChannel *channel, imgIDecoderObserver *aObserver, nsISupports *aCX, nsIStreamListener **listener, imgIRequest **_retval)
 {
   NS_ASSERTION(channel, "imgLoader::LoadImageWithChannel -- NULL channel pointer");
 
+#ifdef DEBUG
+  nsCOMPtr<nsILoadContext> loadContext;
+  NS_QueryNotificationCallbacks(channel, loadContext);
+  MOZ_ASSERT_IF(loadContext, loadContext->UsePrivateBrowsing() == mRespectPrivacy);
+#endif
+
   nsRefPtr<imgRequest> request;
 
   nsCOMPtr<nsIURI> uri;
   channel->GetURI(getter_AddRefs(uri));
 
   nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
   channel->GetLoadFlags(&requestFlags);
 
@@ -1793,18 +1857,18 @@ NS_IMETHODIMP imgLoader::LoadImageWithCh
 
       if (request && entry) {
         // If this entry has no proxies, its request has no reference to the entry.
         if (entry->HasNoProxies()) {
           LOG_FUNC_WITH_PARAM(gImgLog, "imgLoader::LoadImageWithChannel() adding proxyless entry", "uri", spec.get());
           NS_ABORT_IF_FALSE(!request->HasCacheEntry(), "Proxyless entry's request has cache entry!");
           request->SetCacheEntry(entry);
 
-          if (gCacheTracker)
-            gCacheTracker->MarkUsed(entry);
+          if (mCacheTracker)
+            mCacheTracker->MarkUsed(entry);
         } 
       }
     }
   }
 
   nsCOMPtr<nsILoadGroup> loadGroup;
   channel->GetLoadGroup(getter_AddRefs(loadGroup));
 
@@ -1821,17 +1885,17 @@ NS_IMETHODIMP imgLoader::LoadImageWithCh
 
     rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
                                   requestFlags, nullptr, _retval);
     static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
   } else {
     // Default to doing a principal check because we don't know who
     // started that load and whether their principal ended up being
     // inherited on the channel.
-    NewRequestAndEntry(true, getter_AddRefs(request), getter_AddRefs(entry));
+    NewRequestAndEntry(true, this, getter_AddRefs(request), getter_AddRefs(entry));
 
     // We use originalURI here to fulfil the imgIRequest contract on GetURI.
     nsCOMPtr<nsIURI> originalURI;
     channel->GetOriginalURI(getter_AddRefs(originalURI));
 
     // No principal specified here, because we're not passed one.
     request->Init(originalURI, uri, channel, channel, entry,
                   aCX, nullptr, imgIRequest::CORS_NONE);
@@ -2033,27 +2097,26 @@ NS_IMETHODIMP ProxyListener::OnDataAvail
 /**
  * http validate class.  check a channel for a 304
  */
 
 NS_IMPL_ISUPPORTS5(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
                    nsIChannelEventSink, nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
-imgLoader imgCacheValidator::sImgLoader;
-
 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
-                                     imgRequest *request, void *aContext,
-                                     bool forcePrincipalCheckForCacheEntry)
+                                     imgLoader* loader, imgRequest *request,
+                                     void *aContext, bool forcePrincipalCheckForCacheEntry)
  : mProgressProxy(progress),
    mRequest(request),
-   mContext(aContext)
+   mContext(aContext),
+   mImgLoader(loader)
 {
-  NewRequestAndEntry(forcePrincipalCheckForCacheEntry,
-                     getter_AddRefs(mNewRequest), getter_AddRefs(mNewEntry));
+  NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest),
+                     getter_AddRefs(mNewEntry));
 }
 
 imgCacheValidator::~imgCacheValidator()
 {
   if (mRequest) {
     mRequest->mValidator = nullptr;
   }
 }
@@ -2146,17 +2209,17 @@ NS_IMETHODIMP imgCacheValidator::OnStart
                     mContext, loadingPrincipal,
                     corsmode);
 
   mDestListener = new ProxyListener(mNewRequest);
 
   // Try to add the new request into the cache. Note that the entry must be in
   // the cache before the proxies' ownership changes, because adding a proxy
   // changes the caching behaviour for imgRequests.
-  sImgLoader.PutIntoCache(originalURI, mNewEntry);
+  mImgLoader->PutIntoCache(originalURI, mNewEntry);
 
   uint32_t count = mProxies.Count();
   for (int32_t i = count-1; i>=0; i--) {
     imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
     proxy->ChangeOwner(mNewRequest);
 
     // Proxies waiting on cache validation should be deferring notifications.
     // Undefer them.
--- a/image/src/imgLoader.h
+++ b/image/src/imgLoader.h
@@ -22,26 +22,29 @@
 #include "nsIChannelPolicy.h"
 #include "nsIProgressEventSink.h"
 #include "nsIChannel.h"
 
 #ifdef LOADER_THREADSAFE
 #include "prlock.h"
 #endif
 
+class imgLoader;
 class imgRequest;
 class imgRequestProxy;
 class imgIRequest;
 class imgIDecoderObserver;
 class nsILoadGroup;
+class imgCacheExpirationTracker;
+class imgMemoryReporter;
 
 class imgCacheEntry
 {
 public:
-  imgCacheEntry(imgRequest *request, bool aForcePrincipalCheck);
+  imgCacheEntry(imgLoader* loader, imgRequest *request, bool aForcePrincipalCheck);
   ~imgCacheEntry();
 
   nsrefcnt AddRef()
   {
     NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt");
     NS_ABORT_IF_FALSE(_mOwningThread.GetThread() == PR_GetCurrentThread(), "imgCacheEntry addref isn't thread-safe!");
     ++mRefCnt;
     NS_LOG_ADDREF(this, mRefCnt, "imgCacheEntry", sizeof(*this));
@@ -125,16 +128,21 @@ public:
     return mHasNoProxies;
   }
 
   bool ForcePrincipalCheck() const
   {
     return mForcePrincipalCheck;
   }
 
+  imgLoader* Loader() const
+  {
+    return mLoader;
+  }
+
 private: // methods
   friend class imgLoader;
   friend class imgCacheQueue;
   void Touch(bool updateTime = true);
   void UpdateCache(int32_t diff = 0);
   void SetEvicted(bool evict)
   {
     mEvicted = evict;
@@ -143,16 +151,17 @@ private: // methods
 
   // Private, unimplemented copy constructor.
   imgCacheEntry(const imgCacheEntry &);
 
 private: // data
   nsAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
+  imgLoader* mLoader;
   nsRefPtr<imgRequest> mRequest;
   uint32_t mDataSize;
   int32_t mTouchedTime;
   int32_t mExpiryTime;
   nsExpirationState mExpirationState;
   bool mMustValidate : 1;
   bool mEvicted : 1;
   bool mHasNoProxies : 1;
@@ -214,28 +223,29 @@ public:
 
   imgLoader();
   virtual ~imgLoader();
 
   nsresult Init();
 
   static nsresult GetMimeTypeFromContent(const char* aContents, uint32_t aLength, nsACString& aContentType);
 
+  static void GlobalInit(); // for use by the factory
   static void Shutdown(); // for use by the factory
 
-  static nsresult ClearChromeImageCache();
-  static nsresult ClearImageCache();
-  static void MinimizeCaches();
+  nsresult ClearChromeImageCache();
+  nsresult ClearImageCache();
+  void MinimizeCaches();
 
-  static nsresult InitCache();
+  nsresult InitCache();
 
-  static bool RemoveFromCache(nsIURI *aKey);
-  static bool RemoveFromCache(imgCacheEntry *entry);
+  bool RemoveFromCache(nsIURI *aKey);
+  bool RemoveFromCache(imgCacheEntry *entry);
 
-  static bool PutIntoCache(nsIURI *key, imgCacheEntry *entry);
+  bool PutIntoCache(nsIURI *key, imgCacheEntry *entry);
 
   // Returns true if we should prefer evicting cache entry |two| over cache
   // entry |one|.
   // This mixes units in the worst way, but provides reasonable results.
   inline static bool CompareCacheEntries(const nsRefPtr<imgCacheEntry> &one,
                                          const nsRefPtr<imgCacheEntry> &two)
   {
     if (!one)
@@ -251,31 +261,31 @@ public:
     double oneweight = double(one->GetDataSize()) * sizeweight -
                        double(one->GetTouchedTime()) * sCacheTimeWeight;
     double twoweight = double(two->GetDataSize()) * sizeweight -
                        double(two->GetTouchedTime()) * sCacheTimeWeight;
 
     return oneweight < twoweight;
   }
 
-  static void VerifyCacheSizes();
+  void VerifyCacheSizes();
 
   // The image loader maintains a hash table of all imgCacheEntries. However,
   // only some of them will be evicted from the cache: those who have no
   // imgRequestProxies watching their imgRequests. 
   //
   // Once an imgRequest has no imgRequestProxies, it should notify us by
   // calling HasNoObservers(), and null out its cache entry pointer.
   // 
   // Upon having a proxy start observing again, it should notify us by calling
   // HasObservers(). The request's cache entry will be re-set before this
   // happens, by calling imgRequest::SetCacheEntry() when an entry with no
   // observers is re-requested.
-  static bool SetHasNoProxies(nsIURI *key, imgCacheEntry *entry);
-  static bool SetHasProxies(nsIURI *key);
+  bool SetHasNoProxies(nsIURI *key, imgCacheEntry *entry);
+  bool SetHasProxies(nsIURI *key);
 
 private: // methods
 
 
   bool ValidateEntry(imgCacheEntry *aEntry, nsIURI *aKey,
                        nsIURI *aInitialDocumentURI, nsIURI *aReferrerURI, 
                        nsILoadGroup *aLoadGroup,
                        imgIDecoderObserver *aObserver, nsISupports *aCX,
@@ -302,37 +312,42 @@ private: // methods
                                     nsLoadFlags aLoadFlags, imgIRequest *aRequestProxy,
                                     imgIRequest **_retval);
 
   void ReadAcceptHeaderPref();
 
 
   typedef nsRefPtrHashtable<nsCStringHashKey, imgCacheEntry> imgCacheTable;
 
-  static nsresult EvictEntries(imgCacheTable &aCacheToClear);
-  static nsresult EvictEntries(imgCacheQueue &aQueueToClear);
+  nsresult EvictEntries(imgCacheTable &aCacheToClear);
+  nsresult EvictEntries(imgCacheQueue &aQueueToClear);
 
-  static imgCacheTable &GetCache(nsIURI *aURI);
-  static imgCacheQueue &GetCacheQueue(nsIURI *aURI);
-  static void CacheEntriesChanged(nsIURI *aURI, int32_t sizediff = 0);
-  static void CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue);
+  imgCacheTable &GetCache(nsIURI *aURI);
+  imgCacheQueue &GetCacheQueue(nsIURI *aURI);
+  void CacheEntriesChanged(nsIURI *aURI, PRInt32 sizediff = 0);
+  void CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue);
 
 private: // data
   friend class imgCacheEntry;
   friend class imgMemoryReporter;
 
-  static imgCacheTable sCache;
-  static imgCacheQueue sCacheQueue;
+  imgCacheTable mCache;
+  imgCacheQueue mCacheQueue;
 
-  static imgCacheTable sChromeCache;
-  static imgCacheQueue sChromeCacheQueue;
+  imgCacheTable mChromeCache;
+  imgCacheQueue mChromeCacheQueue;
+
   static double sCacheTimeWeight;
   static uint32_t sCacheMaxSize;
+  static imgMemoryReporter* sMemReporter;
 
   nsCString mAcceptHeader;
+
+  nsAutoPtr<imgCacheExpirationTracker> mCacheTracker;
+  bool mRespectPrivacy;
 };
 
 
 
 /**
  * proxy stream listener class used to handle multipart/x-mixed-replace
  */
 
@@ -390,18 +405,18 @@ class nsProgressNotificationProxy MOZ_FI
 #include "nsCOMArray.h"
 
 class imgCacheValidator : public nsIStreamListener,
                           public nsIChannelEventSink,
                           public nsIInterfaceRequestor,
                           public nsIAsyncVerifyRedirectCallback
 {
 public:
-  imgCacheValidator(nsProgressNotificationProxy* progress, imgRequest *request,
-                    void *aContext, bool forcePrincipalCheckForCacheEntry);
+  imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader,
+                    imgRequest *request, void *aContext, bool forcePrincipalCheckForCacheEntry);
   virtual ~imgCacheValidator();
 
   void AddProxy(imgRequestProxy *aProxy);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
@@ -417,12 +432,12 @@ private:
   nsRefPtr<imgRequest> mRequest;
   nsCOMArray<imgIRequest> mProxies;
 
   nsRefPtr<imgRequest> mNewRequest;
   nsRefPtr<imgCacheEntry> mNewEntry;
 
   void *mContext;
 
-  static imgLoader sImgLoader;
+  imgLoader* mImgLoader;
 };
 
 #endif  // imgLoader_h__
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -74,18 +74,18 @@ PRLogModuleInfo *gImgLog = PR_NewLogModu
 NS_IMPL_ISUPPORTS8(imgRequest,
                    imgIDecoderObserver, imgIContainerObserver,
                    nsIStreamListener, nsIRequestObserver,
                    nsISupportsWeakReference,
                    nsIChannelEventSink,
                    nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
-imgRequest::imgRequest() : 
-  mValidator(nullptr), mImageSniffers("image-sniffing-services"),
+imgRequest::imgRequest(imgLoader* aLoader) :
+  mLoader(aLoader), mValidator(nullptr), mImageSniffers("image-sniffing-services"),
   mInnerWindowId(0), mCORSMode(imgIRequest::CORS_NONE),
   mDecodeRequested(false), mIsMultiPartChannel(false), mGotData(false),
   mIsInCache(false), mBlockingOnload(false)
 {
   // Register our pref observers if we haven't yet.
   if (NS_UNLIKELY(!gInitializedPrefCaches)) {
     InitPrefCaches();
   }
@@ -173,17 +173,17 @@ nsresult imgRequest::AddProxy(imgRequest
 {
   NS_PRECONDITION(proxy, "null imgRequestProxy passed in");
   LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
 
   // If we're empty before adding, we have to tell the loader we now have
   // proxies.
   if (mObservers.IsEmpty()) {
     NS_ABORT_IF_FALSE(mURI, "Trying to SetHasProxies without key uri.");
-    imgLoader::SetHasProxies(mURI);
+    mLoader->SetHasProxies(mURI);
   }
 
   // If we don't have any current observers, we should restart any animation.
   if (mImage && !HaveProxyWithObserver(proxy) && proxy->HasObserver()) {
     LOG_MSG(gImgLog, "imgRequest::AddProxy", "resetting animation");
 
     mImage->ResetAnimation();
   }
@@ -218,17 +218,17 @@ nsresult imgRequest::RemoveProxy(imgRequ
 
   if (mObservers.IsEmpty()) {
     // If we have no observers, there's nothing holding us alive. If we haven't
     // been cancelled and thus removed from the cache, tell the image loader so
     // we can be evicted from the cache.
     if (mCacheEntry) {
       NS_ABORT_IF_FALSE(mURI, "Removing last observer without key uri.");
 
-      imgLoader::SetHasNoProxies(mURI, mCacheEntry);
+      mLoader->SetHasNoProxies(mURI, mCacheEntry);
     } 
 #if defined(PR_LOGGING)
     else {
       nsCAutoString spec;
       mURI->GetSpec(spec);
       LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry", "uri", spec.get());
     }
 #endif
@@ -323,19 +323,19 @@ nsresult imgRequest::GetSecurityInfo(nsI
 
 void imgRequest::RemoveFromCache()
 {
   LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache");
 
   if (mIsInCache) {
     // mCacheEntry is nulled out when we have no more observers.
     if (mCacheEntry)
-      imgLoader::RemoveFromCache(mCacheEntry);
+      mLoader->RemoveFromCache(mCacheEntry);
     else
-      imgLoader::RemoveFromCache(mURI);
+      mLoader->RemoveFromCache(mURI);
   }
 
   mCacheEntry = nullptr;
 }
 
 bool imgRequest::HaveProxyWithObserver(imgRequestProxy* aProxyToIgnore) const
 {
   nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mObservers);
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -26,16 +26,17 @@
 #include "nsWeakReference.h"
 #include "nsError.h"
 #include "imgIRequest.h"
 #include "imgStatusTracker.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 
 class imgCacheValidator;
 
+class imgLoader;
 class imgRequestProxy;
 class imgCacheEntry;
 class imgMemoryReporter;
 class imgRequestNotifyRunnable;
 
 namespace mozilla {
 namespace image {
 class Image;
@@ -45,17 +46,17 @@ class Image;
 class imgRequest : public imgIDecoderObserver,
                    public nsIStreamListener,
                    public nsSupportsWeakReference,
                    public nsIChannelEventSink,
                    public nsIInterfaceRequestor,
                    public nsIAsyncVerifyRedirectCallback
 {
 public:
-  imgRequest();
+  imgRequest(imgLoader* aLoader);
   virtual ~imgRequest();
 
   NS_DECL_ISUPPORTS
 
   nsresult Init(nsIURI *aURI,
                 nsIURI *aCurrentURI,
                 nsIRequest *aRequest,
                 nsIChannel *aChannel,
@@ -178,16 +179,18 @@ public:
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
 private:
   friend class imgMemoryReporter;
 
+  // Weak reference to parent loader; this request cannot outlive its owner.
+  imgLoader* mLoader;
   nsCOMPtr<nsIRequest> mRequest;
   // The original URI we were loaded with. This is the same as the URI we are
   // keyed on in the cache.
   nsCOMPtr<nsIURI> mURI;
   // The URI of the resource we ended up loading after all redirects, etc.
   nsCOMPtr<nsIURI> mCurrentURI;
   // The principal of the document which loaded this image. Used when validating for CORS.
   nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
--- a/image/src/imgTools.cpp
+++ b/image/src/imgTools.cpp
@@ -3,31 +3,37 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "imgTools.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsError.h"
+#include "imgILoader.h"
+#include "imgICache.h"
 #include "imgIContainer.h"
 #include "imgIEncoder.h"
 #include "imgIDecoderObserver.h"
 #include "imgIContainerObserver.h"
 #include "gfxContext.h"
 #include "nsStringStream.h"
 #include "nsComponentManagerUtils.h"
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsStreamUtils.h"
 #include "nsNetUtil.h"
+#include "nsContentUtils.h"
 #include "RasterImage.h"
 
 using namespace mozilla::image;
 
+class nsIDOMDocument;
+class nsIDocument;
+
 /* ========== imgITools implementation ========== */
 
 
 
 NS_IMPL_ISUPPORTS1(imgTools, imgITools)
 
 imgTools::imgTools()
 {
@@ -264,8 +270,25 @@ NS_IMETHODIMP imgTools::GetFirstImageFra
                                       getter_AddRefs(frame));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(frame, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(frame->Width() && frame->Height(), NS_ERROR_FAILURE);
 
   frame.forget(aSurface);
   return NS_OK;
 }
+
+NS_IMETHODIMP
+imgTools::GetImgLoaderForDocument(nsIDOMDocument* aDoc, imgILoader** aLoader)
+{
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
+  NS_IF_ADDREF(*aLoader = nsContentUtils::GetImgLoaderForDocument(doc));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+imgTools::GetImgCacheForDocument(nsIDOMDocument* aDoc, imgICache** aCache)
+{
+  nsCOMPtr<imgILoader> loader;
+  nsresult rv = GetImgLoaderForDocument(aDoc, getter_AddRefs(loader));
+  NS_ENSURE_SUCCESS(rv, rv);
+  return CallQueryInterface(loader, aCache);
+}
--- a/image/test/mochitest/imgutils.js
+++ b/image/test/mochitest/imgutils.js
@@ -2,19 +2,20 @@
 // Helper file for shared image functionality
 // 
 // Note that this is use by tests elsewhere in the source tree. When in doubt,
 // check mxr before removing or changing functionality.
 
 // Helper function to clear the image cache of content images
 function clearImageCache()
 {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  var imageCache = Components.classes["@mozilla.org/image/cache;1"]
-                             .getService(Components.interfaces.imgICache);
+  var tools = SpecialPowers.wrap(Components)
+                             .classes["@mozilla.org/image/tools;1"]
+                             .getService(Components.interfaces.imgITools);
+  var imageCache = tools.getImgCacheForDocument(window.document);
   imageCache.clearCache(false); // true=chrome, false=content
 }
 
 // Helper function to determine if the frame is decoded for a given image id
 function isFrameDecoded(id)
 {
   return (getImageStatus(id) &
           Components.interfaces.imgIRequest.STATUS_FRAME_COMPLETE)
--- a/image/test/unit/async_load_tests.js
+++ b/image/test/unit/async_load_tests.js
@@ -80,19 +80,18 @@ function secondLoadDone(oldlistener, aRe
 }
 
 // Load the request a second time. This should come from the image cache, and
 // therefore would be at most risk of being served synchronously.
 function checkSecondLoad()
 {
   do_test_pending();
 
-  var loader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
   var listener = new ImageListener(checkClone, secondLoadDone);
-  requests.push(loader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null));
+  requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null));
   listener.synchronous = false;
 }
 
 function firstLoadDone(oldlistener, aRequest)
 {
   checkSecondLoad(uri);
 
   do_test_finished();
@@ -134,81 +133,76 @@ function checkSecondChannelLoad()
 {
   do_test_pending();
 
   var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);  
   var channel = ioService.newChannelFromURI(uri);
   var channellistener = new ChannelListener();
   channel.asyncOpen(channellistener, null);
 
-  var loader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
   var listener = new ImageListener(getChannelLoadImageStartCallback(channellistener),
                                    getChannelLoadImageStopCallback(channellistener,
                                                                    all_done_callback));
   var outlistener = {};
-  requests.push(loader.loadImageWithChannel(channel, listener, null, outlistener));
+  requests.push(gCurrentLoader.loadImageWithChannel(channel, listener, null, outlistener));
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function run_loadImageWithChannel_tests()
 {
-  // To ensure we're testing what we expect to, clear the content image cache
-  // between test runs.
-  var loader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
-  loader.QueryInterface(Ci.imgICache);
-  loader.clearCache(false);
+  // To ensure we're testing what we expect to, create a new loader and cache.
+  gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
 
   do_test_pending();
 
   var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);  
   var channel = ioService.newChannelFromURI(uri);
   var channellistener = new ChannelListener();
   channel.asyncOpen(channellistener, null);
 
-  var loader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
   var listener = new ImageListener(getChannelLoadImageStartCallback(channellistener),
                                    getChannelLoadImageStopCallback(channellistener,
                                                                    checkSecondChannelLoad));
   var outlistener = {};
-  requests.push(loader.loadImageWithChannel(channel, listener, null, outlistener));
+  requests.push(gCurrentLoader.loadImageWithChannel(channel, listener, null, outlistener));
   channellistener.outputListener = outlistener.value;
 
   listener.synchronous = false;
 }
 
 function all_done_callback()
 {
   server.stop(function() { do_test_finished(); });
 }
 
 function startImageCallback(otherCb)
 {
   return function(listener, request)
   {
-    var loader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
-
     // Make sure we can load the same image immediately out of the cache.
     do_test_pending();
     var listener2 = new ImageListener(null, function(foo, bar) { do_test_finished(); });
-    requests.push(loader.loadImage(uri, null, null, null, null, listener2, null, 0, null, null, null));
+    requests.push(gCurrentLoader.loadImage(uri, null, null, null, null, listener2, null, 0, null, null, null));
     listener2.synchronous = false;
 
     // Now that we've started another load, chain to the callback.
     otherCb(listener, request);
   }
 }
 
+var gCurrentLoader;
+
 function run_test()
 {
-  var loader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
+  gCurrentLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
 
   do_test_pending();
   var listener = new ImageListener(startImageCallback(checkClone), firstLoadDone);
-  var req = loader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null);
+  var req = gCurrentLoader.loadImage(uri, null, null, null, null, listener, null, 0, null, null, null);
   requests.push(req);
 
   // Ensure that we don't cause any mayhem when we lock an image.
   req.lockImage();
 
   listener.synchronous = false;
 }
--- a/image/test/unit/test_private_channel.js
+++ b/image/test/unit/test_private_channel.js
@@ -4,17 +4,19 @@ var server = new nsHttpServer();
 server.registerPathHandler('/image.png', imageHandler);
 server.start(8088);
 
 load('image_load_helpers.js');
 
 var gHits = 0;
 
 var gIoService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);  
-var gLoader = Cc["@mozilla.org/image/loader;1"].getService(Ci.imgILoader);
+var gPublicLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
+var gPrivateLoader = Cc["@mozilla.org/image/loader;1"].createInstance(Ci.imgILoader);
+gPrivateLoader.QueryInterface(Ci.imgICache).respectPrivacyNotifications();
 
 function imageHandler(metadata, response) {
   gHits++;
   response.setHeader("Cache-Control", "max-age=10000", false);
   response.setStatusLine(metadata.httpVersion, 200, "OK");
   response.setHeader("Content-Type", "image/png", false);
   var body = "iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAEUlEQVQImWP4z8AAQTAamQkAhpcI+DeMzFcAAAAASUVORK5CYII=";
   response.bodyOutputStream.write(body, body.length);
@@ -48,56 +50,50 @@ function setup_chan(path, isPrivate, cal
   var chan = gIoService.newChannelFromURI(uri);
   chan.notificationCallbacks = new NotificationCallbacks(isPrivate);
   var channelListener = new ChannelListener();
   chan.asyncOpen(channelListener, null);
   
   var listener = new ImageListener(null, callback);
   listeners.push(listener);
   var outlistener = {};
-  requests.push(gLoader.loadImageWithChannel(chan, listener, null, outlistener));
+  var loader = isPrivate ? gPrivateLoader : gPublicLoader;
+  requests.push(loader.loadImageWithChannel(chan, listener, null, outlistener));
   channelListener.outputListener = outlistener.value;
   listener.synchronous = false;
 }
 
 function loadImage(isPrivate, callback) {
   var listener = new ImageListener(null, callback);
   var uri = gIoService.newURI(gImgPath, null, null);
   var loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
   loadGroup.notificationCallbacks = new NotificationCallbacks(isPrivate);
-  requests.push(gLoader.loadImage(uri, null, null, null, loadGroup, listener, null, 0, null, null, null));
+  var loader = isPrivate ? gPrivateLoader : gPublicLoader;
+  requests.push(loader.loadImage(uri, null, null, null, loadGroup, listener, null, 0, null, null, null));
   listener.synchronous = false;  
 }
 
 function run_loadImage_tests() {
-  clearAllImageCaches();
   let cs = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
   cs.evictEntries(Ci.nsICache.STORE_ANYWHERE);
 
   gHits = 0;
   loadImage(false, function() {
     loadImage(false, function() {
       loadImage(true, function() {
         loadImage(true, function() {
           do_check_eq(gHits, 2);
-          do_test_finished();
+          server.stop(do_test_finished);
         });
       });
     });
   });
 }
 
-function clearAllImageCaches() {
-  gLoader.QueryInterface(Ci.imgICache);
-  gLoader.clearCache(false, false);
-  gLoader.clearCache(false, true);
-}
-
 function run_test() {
-  clearAllImageCaches();
   do_test_pending();
  
   // We create a public channel that loads an image, then an identical
   // one that should cause a cache read. We then create a private channel
   // and load the same image, and do that a second time to ensure a cache
   // read. In total, we should cause two separate http responses to occur,
   // since the private channels shouldn't be able to use the public cache.
   setup_chan('/image.png', false, function() {
--- a/layout/build/nsContentDLF.cpp
+++ b/layout/build/nsContentDLF.cpp
@@ -476,13 +476,13 @@ nsContentDLF::CreateXULDocument(const ch
    * Bind the document to the Content Viewer...
    */
   rv = contentViewer->LoadStart(doc);
   contentViewer.forget(aContentViewer);
   return rv;
 }
 
 bool nsContentDLF::IsImageContentType(const char* aContentType) {
-  nsCOMPtr<imgILoader> loader(do_GetService("@mozilla.org/image/loader;1"));
+  nsCOMPtr<imgILoader> loader(do_CreateInstance("@mozilla.org/image/loader;1"));
   bool isDecoderAvailable = false;
   loader->SupportImageWithMimeType(aContentType, &isDecoderAvailable);
   return isDecoderAvailable;
 }
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -1781,18 +1781,18 @@ nsImageFrame::LoadIcon(const nsAString& 
   if (!sIOService) {
     rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<nsIURI> realURI;
   SpecToURI(aSpec, sIOService, getter_AddRefs(realURI));
  
-  nsCOMPtr<imgILoader> il(do_GetService("@mozilla.org/image/loader;1", &rv));
-  if (NS_FAILED(rv)) return rv;
+  nsCOMPtr<imgILoader> il =
+    nsContentUtils::GetImgLoaderForDocument(aPresContext->Document());
 
   nsCOMPtr<nsILoadGroup> loadGroup;
   GetLoadGroup(aPresContext, getter_AddRefs(loadGroup));
 
   // For icon loads, we don't need to merge with the loadgroup flags
   nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
 
   return il->LoadImage(realURI,     /* icon URI */
--- a/widget/cocoa/nsMenuItemIconX.mm
+++ b/widget/cocoa/nsMenuItemIconX.mm
@@ -24,16 +24,17 @@
 #include "nsToolkit.h"
 #include "nsNetUtil.h"
 #include "imgILoader.h"
 #include "imgIRequest.h"
 #include "nsMenuItemX.h"
 #include "gfxImageSurface.h"
 #include "imgIContainer.h"
 #include "nsCocoaUtils.h"
+#include "nsContentUtils.h"
 
 static const uint32_t kIconWidth = 16;
 static const uint32_t kIconHeight = 16;
 static const uint32_t kIconBitsPerComponent = 8;
 static const uint32_t kIconComponents = 4;
 static const uint32_t kIconBitsPerPixel = kIconBitsPerComponent *
                                           kIconComponents;
 static const uint32_t kIconBytesPerRow = kIconWidth * kIconBitsPerPixel / 8;
@@ -273,20 +274,18 @@ nsMenuItemIconX::LoadIcon(nsIURI* aIconU
 
   if (!mContent) return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIDocument> document = mContent->OwnerDoc();
 
   nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup();
   if (!loadGroup) return NS_ERROR_FAILURE;
 
-  nsresult rv = NS_ERROR_FAILURE;
-  nsCOMPtr<imgILoader> loader = do_GetService("@mozilla.org/image/loader;1",
-                                              &rv);
-  if (NS_FAILED(rv)) return rv;
+  nsCOMPtr<imgILoader> loader = nsContentUtils::GetImgLoaderForDocument(document);
+  if (!loader) return NS_ERROR_FAILURE;
 
   if (!mSetIcon) {
     // Set a completely transparent 16x16 image as the icon on this menu item
     // as a placeholder.  This keeps the menu item text displayed in the same
     // position that it will be displayed when the real icon is loaded, and
     // prevents it from jumping around or looking misaligned.
 
     static bool sInitializedPlaceholder;
@@ -301,19 +300,19 @@ nsMenuItemIconX::LoadIcon(nsIURI* aIconU
     if (!sPlaceholderIconImage) return NS_ERROR_FAILURE;
 
     if (mNativeMenuItem)
       [mNativeMenuItem setImage:sPlaceholderIconImage];
   }
 
   // Passing in null for channelPolicy here since nsMenuItemIconX::LoadIcon is
   // not exposed to web content
-  rv = loader->LoadImage(aIconURI, nullptr, nullptr, nullptr, loadGroup, this,
-                         nullptr, nsIRequest::LOAD_NORMAL, nullptr, nullptr,
-                         nullptr, getter_AddRefs(mIconRequest));
+  nsresult rv = loader->LoadImage(aIconURI, nullptr, nullptr, nullptr, loadGroup, this,
+                                   nullptr, nsIRequest::LOAD_NORMAL, nullptr, nullptr,
+                                   nullptr, getter_AddRefs(mIconRequest));
   if (NS_FAILED(rv)) return rv;
 
   // We need to request the icon be decoded (bug 573583, bug 705516).
   mIconRequest->RequestDecode();
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;