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 110466 6b4ab96903f2b3144f5598e2813a3237a515206e
parent 110465 c3333e6e37aa746ccf694a9faa8091d5112988ab
child 110467 da1ba554adb87fe44e9943851db027087d254125
push id239
push userakeybl@mozilla.com
push dateThu, 03 Jan 2013 21:54:43 +0000
treeherdermozilla-release@3a7b66445659 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoe
bugs722861
milestone17.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 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;