Bug 867755 - Support OnDataAvailable and OnStopRequest off main thread for image loading r=seth
authorSteve Workman <sworkman@mozilla.com>
Sat, 28 Sep 2013 11:28:42 -0700
changeset 149158 799dffa9306bf8c3311b0f827a09e78eaa93e56f
parent 149157 736a590cb652fcc98eaeedb494fa419280d515c8
child 149159 68378e8901f30478d03d6e093a95587706bee0f0
push id25374
push usercbook@mozilla.com
push dateSun, 29 Sep 2013 09:37:16 +0000
treeherdermozilla-central@8f805d3ef377 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseth
bugs867755
milestone27.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 867755 - Support OnDataAvailable and OnStopRequest off main thread for image loading r=seth
image/build/nsImageModule.cpp
image/src/Image.cpp
image/src/Image.h
image/src/ImageFactory.cpp
image/src/ImageFactory.h
image/src/ImageURL.h
image/src/ImageWrapper.cpp
image/src/ImageWrapper.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/VectorImage.h
image/src/imgLoader.cpp
image/src/imgLoader.h
image/src/imgRequest.cpp
image/src/imgRequest.h
image/src/imgRequestProxy.cpp
image/src/imgRequestProxy.h
image/src/imgStatusTracker.cpp
netwerk/base/src/nsMediaFragmentURIParser.cpp
netwerk/base/src/nsMediaFragmentURIParser.h
--- a/image/build/nsImageModule.cpp
+++ b/image/build/nsImageModule.cpp
@@ -2,16 +2,17 @@
  *
  * 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 "mozilla/ModuleUtils.h"
 #include "nsMimeTypes.h"
 
+#include "ImageFactory.h"
 #include "RasterImage.h"
 
 #include "imgLoader.h"
 #include "imgRequest.h"
 #include "imgRequestProxy.h"
 #include "imgTools.h"
 #include "DiscardTracker.h"
 
@@ -76,16 +77,17 @@ static const mozilla::Module::CategoryEn
   { "content-sniffing-services", "@mozilla.org/image/loader;1", "@mozilla.org/image/loader;1" },
   { nullptr }
 };
 
 static nsresult
 imglib_Initialize()
 {
   mozilla::image::DiscardTracker::Initialize();
+  mozilla::image::ImageFactory::Initialize();
   mozilla::image::RasterImage::Initialize();
   imgLoader::GlobalInit();
   return NS_OK;
 }
 
 static void
 imglib_Shutdown()
 {
--- a/image/src/Image.cpp
+++ b/image/src/Image.cpp
@@ -5,25 +5,20 @@
 
 #include "nsMimeTypes.h"
 
 #include "Image.h"
 
 namespace mozilla {
 namespace image {
 
-#ifdef DEBUG_imagelib
-static const bool allowOMTURIAccess = true;
-#else
-static const bool allowOMTURIAccess = false;
-#endif
-
 // Constructor
-ImageResource::ImageResource(imgStatusTracker* aStatusTracker, nsIURI* aURI) :
-  mURI(new nsMainThreadPtrHolder<nsIURI>(aURI, allowOMTURIAccess)),
+ImageResource::ImageResource(imgStatusTracker* aStatusTracker,
+                             ImageURL* aURI) :
+  mURI(aURI),
   mInnerWindowId(0),
   mAnimationConsumers(0),
   mAnimationMode(kNormalAnimMode),
   mInitialized(false),
   mAnimating(false),
   mError(false)
 {
   if (aStatusTracker) {
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -4,18 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_IMAGELIB_IMAGE_H_
 #define MOZILLA_IMAGELIB_IMAGE_H_
 
 #include "mozilla/MemoryReporting.h"
 #include "imgIContainer.h"
 #include "imgStatusTracker.h"
-#include "nsIURI.h"
-#include "nsProxyRelease.h" // for nsMainThreadPtrHolder and nsMainThreadPtrHandle
+#include "ImageURL.h"
 
 class nsIRequest;
 class nsIInputStream;
 
 namespace mozilla {
 namespace image {
 
 class Image : public imgIContainer
@@ -127,17 +126,17 @@ public:
   virtual nsresult OnNewSourceData() = 0;
 
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
   virtual uint64_t InnerWindowID() const = 0;
 
   virtual bool HasError() = 0;
   virtual void SetHasError() = 0;
 
-  virtual nsIURI* GetURI() = 0;
+  virtual ImageURL* GetURI() = 0;
 };
 
 class ImageResource : public Image
 {
 public:
   virtual imgStatusTracker& GetStatusTracker() MOZ_OVERRIDE { return *mStatusTracker; }
   virtual uint32_t SizeOfData() MOZ_OVERRIDE;
 
@@ -154,20 +153,21 @@ public:
 
   virtual bool HasError() MOZ_OVERRIDE    { return mError; }
   virtual void SetHasError() MOZ_OVERRIDE { mError = true; }
 
   /*
    * Returns a non-AddRefed pointer to the URI associated with this image.
    * Illegal to use off-main-thread.
    */
-  virtual nsIURI* GetURI() MOZ_OVERRIDE { return mURI.get(); }
+  virtual ImageURL* GetURI() MOZ_OVERRIDE { return mURI.get(); }
 
 protected:
-  ImageResource(imgStatusTracker* aStatusTracker, nsIURI* aURI);
+  ImageResource(imgStatusTracker* aStatusTracker,
+                ImageURL* aURI);
 
   // Shared functionality for implementors of imgIContainer. Every
   // implementation of attribute animationMode should forward here.
   nsresult GetAnimationModeInternal(uint16_t *aAnimationMode);
   nsresult SetAnimationModeInternal(uint16_t aAnimationMode);
 
   /**
    * Decides whether animation should or should not be happening,
@@ -183,17 +183,17 @@ protected:
     return mAnimationConsumers > 0 && mAnimationMode != kDontAnimMode;
   }
 
   virtual nsresult StartAnimation() = 0;
   virtual nsresult StopAnimation() = 0;
 
   // Member data shared by all implementations of this abstract class
   nsRefPtr<imgStatusTracker>    mStatusTracker;
-  nsMainThreadPtrHandle<nsIURI> mURI;
+  nsRefPtr<ImageURL>            mURI;
   uint64_t                      mInnerWindowId;
   uint32_t                      mAnimationConsumers;
   uint16_t                      mAnimationMode; // Enum values in imgIContainer
   bool                          mInitialized:1; // Have we been initalized?
   bool                          mAnimating:1;   // Are we currently animating?
   bool                          mError:1;       // Error handling
 };
 
--- a/image/src/ImageFactory.cpp
+++ b/image/src/ImageFactory.cpp
@@ -8,17 +8,16 @@
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Likely.h"
 
 #include "nsIHttpChannel.h"
 #include "nsIFileChannel.h"
 #include "nsIFile.h"
 #include "nsMimeTypes.h"
-#include "nsIURI.h"
 #include "nsIRequest.h"
 
 #include "RasterImage.h"
 #include "VectorImage.h"
 #include "Image.h"
 #include "nsMediaFragmentURIParser.h"
 
 #include "ImageFactory.h"
@@ -26,26 +25,29 @@
 namespace mozilla {
 namespace image {
 
 // Global preferences related to image containers.
 static bool gInitializedPrefCaches = false;
 static bool gDecodeOnDraw = false;
 static bool gDiscardable = false;
 
-static void
-InitPrefCaches()
+/*static*/ void
+ImageFactory::Initialize()
 {
-  Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable");
-  Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
-  gInitializedPrefCaches = true;
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!gInitializedPrefCaches) {
+    Preferences::AddBoolVarCache(&gDiscardable, "image.mem.discardable");
+    Preferences::AddBoolVarCache(&gDecodeOnDraw, "image.mem.decodeondraw");
+    gInitializedPrefCaches = true;
+  }
 }
 
 static uint32_t
-ComputeImageFlags(nsIURI* uri, bool isMultiPart)
+ComputeImageFlags(ImageURL* uri, bool isMultiPart)
 {
   nsresult rv;
 
   // We default to the static globals.
   bool isDiscardable = gDiscardable;
   bool doDecodeOnDraw = gDecodeOnDraw;
 
   // We want UI to be as snappy as possible and not to flicker. Disable discarding
@@ -78,23 +80,22 @@ ComputeImageFlags(nsIURI* uri, bool isMu
 
   return imageFlags;
 }
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateImage(nsIRequest* aRequest,
                           imgStatusTracker* aStatusTracker,
                           const nsCString& aMimeType,
-                          nsIURI* aURI,
+                          ImageURL* aURI,
                           bool aIsMultiPart,
                           uint32_t aInnerWindowId)
 {
-  // Register our pref observers if we haven't yet.
-  if (MOZ_UNLIKELY(!gInitializedPrefCaches))
-    InitPrefCaches();
+  MOZ_ASSERT(gInitializedPrefCaches,
+             "Pref observers should have been initialized already");
 
   // Compute the image's initialization flags.
   uint32_t imageFlags = ComputeImageFlags(aURI, aIsMultiPart);
 
   // Select the type of image to create based on MIME type.
   if (aMimeType.EqualsLiteral(IMAGE_SVG_XML)) {
     return CreateVectorImage(aRequest, aStatusTracker, aMimeType,
                              aURI, imageFlags, aInnerWindowId);
@@ -170,17 +171,17 @@ GetContentSize(nsIRequest* aRequest)
   // Fallback - neither http nor file. We'll use dynamic allocation.
   return 0;
 }
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateRasterImage(nsIRequest* aRequest,
                                 imgStatusTracker* aStatusTracker,
                                 const nsCString& aMimeType,
-                                nsIURI* aURI,
+                                ImageURL* aURI,
                                 uint32_t aImageFlags,
                                 uint32_t aInnerWindowId)
 {
   nsresult rv;
 
   nsRefPtr<RasterImage> newImage = new RasterImage(aStatusTracker, aURI);
 
   rv = newImage->Init(aMimeType.get(), aImageFlags);
@@ -201,29 +202,31 @@ ImageFactory::CreateRasterImage(nsIReque
       nsresult rv2 = newImage->SetSourceSizeHint(sizeHint);
       // If we've still failed at this point, things are going downhill.
       if (NS_FAILED(rv) || NS_FAILED(rv2)) {
         NS_WARNING("About to hit OOM in imagelib!");
       }
     }
   }
 
-  mozilla::net::nsMediaFragmentURIParser parser(aURI);
+  nsAutoCString ref;
+  aURI->GetRef(ref);
+  mozilla::net::nsMediaFragmentURIParser parser(ref);
   if (parser.HasResolution()) {
     newImage->SetRequestedResolution(parser.GetResolution());
   }
 
   return newImage.forget();
 }
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateVectorImage(nsIRequest* aRequest,
                                 imgStatusTracker* aStatusTracker,
                                 const nsCString& aMimeType,
-                                nsIURI* aURI,
+                                ImageURL* aURI,
                                 uint32_t aImageFlags,
                                 uint32_t aInnerWindowId)
 {
   nsresult rv;
 
   nsRefPtr<VectorImage> newImage = new VectorImage(aStatusTracker, aURI);
 
   rv = newImage->Init(aMimeType.get(), aImageFlags);
--- a/image/src/ImageFactory.h
+++ b/image/src/ImageFactory.h
@@ -3,67 +3,74 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_IMAGELIB_IMAGEFACTORY_H_
 #define MOZILLA_IMAGELIB_IMAGEFACTORY_H_
 
 #include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
 
 class nsCString;
 class nsIRequest;
-class nsIURI;
 class imgStatusTracker;
 
 namespace mozilla {
 namespace image {
 
 class Image;
+class ImageURL;
 
 class ImageFactory
 {
 public:
   /**
+   * Registers vars with Preferences. Should only be called on the main thread.
+   */
+  static void Initialize();
+
+  /**
    * Creates a new image with the given properties.
+   * Can be called on or off the main thread.
    *
    * @param aRequest       The associated request.
    * @param aStatusTracker A status tracker for the image to use.
    * @param aMimeType      The mimetype of the image.
    * @param aURI           The URI of the image.
    * @param aIsMultiPart   Whether the image is part of a multipart request.
    * @param aInnerWindowId The window this image belongs to.
    */
   static already_AddRefed<Image> CreateImage(nsIRequest* aRequest,
                                              imgStatusTracker* aStatusTracker,
                                              const nsCString& aMimeType,
-                                             nsIURI* aURI,
+                                             ImageURL* aURI,
                                              bool aIsMultiPart,
                                              uint32_t aInnerWindowId);
   /**
    * Creates a new image which isn't associated with a URI or loaded through
    * the usual image loading mechanism.
    *
    * @param aMimeType      The mimetype of the image.
    */
   static already_AddRefed<Image> CreateAnonymousImage(const nsCString& aMimeType);
 
 private:
   // Factory functions that create specific types of image containers.
   static already_AddRefed<Image> CreateRasterImage(nsIRequest* aRequest,
                                                    imgStatusTracker* aStatusTracker,
                                                    const nsCString& aMimeType,
-                                                   nsIURI* aURI,
+                                                   ImageURL* aURI,
                                                    uint32_t aImageFlags,
                                                    uint32_t aInnerWindowId);
 
   static already_AddRefed<Image> CreateVectorImage(nsIRequest* aRequest,
                                                    imgStatusTracker* aStatusTracker,
                                                    const nsCString& aMimeType,
-                                                   nsIURI* aURI,
+                                                   ImageURL* aURI,
                                                    uint32_t aImageFlags,
                                                    uint32_t aInnerWindowId);
 
   // This is a static factory class, so disallow instantiation.
   virtual ~ImageFactory() = 0;
 };
 
 } // namespace image
new file mode 100644
--- /dev/null
+++ b/image/src/ImageURL.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef MOZILLA_IMAGELIB_IMAGEURL_H_
+#define MOZILLA_IMAGELIB_IMAGEURL_H_
+
+#include "nsIURI.h"
+#include "MainThreadUtils.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+namespace image {
+
+/** ImageURL
+ *
+ * nsStandardURL is not threadsafe, so this class is created to hold only the
+ * necessary URL data required for image loading and decoding.
+ *
+ * Note: Although several APIs have the same or similar prototypes as those
+ * found in nsIURI/nsStandardURL, the class does not implement nsIURI. This is
+ * intentional; functionality is limited, and is only useful for imagelib code.
+ * By not implementing nsIURI, external code cannot unintentionally be given an
+ * nsIURI pointer with this limited class behind it; instead, conversion to a
+ * fully implemented nsIURI is required (e.g. through NS_NewURI).
+ */
+class ImageURL
+{
+public:
+  explicit ImageURL(nsIURI* aURI) {
+    MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
+    aURI->GetSpec(mSpec);
+    aURI->GetScheme(mScheme);
+    aURI->GetRef(mRef);
+  }
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::image::ImageURL)
+
+  nsresult GetSpec(nsACString &result) {
+    result = mSpec;
+    return NS_OK;
+  }
+
+  nsresult GetScheme(nsACString &result) {
+    result = mScheme;
+    return NS_OK;
+  }
+
+  nsresult SchemeIs(const char *scheme, bool *result)
+  {
+    NS_PRECONDITION(scheme, "scheme is null");
+    NS_PRECONDITION(result, "result is null");
+
+    *result = mScheme.Equals(scheme);
+    return NS_OK;
+  }
+
+  nsresult GetRef(nsACString &result) {
+    result = mRef;
+    return NS_OK;
+  }
+
+  already_AddRefed<nsIURI> ToIURI() {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "Convert to nsIURI on main thread only; it is not threadsafe.");
+    nsCOMPtr<nsIURI> newURI;
+    NS_NewURI(getter_AddRefs(newURI), mSpec);
+    return newURI.forget();
+  }
+
+private:
+  // Since this is a basic storage class, no duplication of spec parsing is
+  // included in the functionality. Instead, the class depends upon the
+  // parsing implementation in the nsIURI class used in object construction.
+  // This means each field is stored separately, but since only a few are
+  // required, this small memory tradeoff for threadsafe usage should be ok.
+  nsAutoCString mSpec;
+  nsAutoCString mScheme;
+  nsAutoCString mRef;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // MOZILLA_IMAGELIB_IMAGEURL_H_
--- a/image/src/ImageWrapper.cpp
+++ b/image/src/ImageWrapper.cpp
@@ -129,17 +129,17 @@ ImageWrapper::HasError()
 }
 
 void
 ImageWrapper::SetHasError()
 {
   mInnerImage->SetHasError();
 }
 
-nsIURI*
+ImageURL*
 ImageWrapper::GetURI()
 {
   return mInnerImage->GetURI();
 }
 
 // Methods inherited from XPCOM interfaces.
 
 NS_IMPL_ISUPPORTS1(ImageWrapper, imgIContainer)
--- a/image/src/ImageWrapper.h
+++ b/image/src/ImageWrapper.h
@@ -53,17 +53,17 @@ public:
   virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
 
   virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE;
   virtual uint64_t InnerWindowID() const MOZ_OVERRIDE;
 
   virtual bool HasError() MOZ_OVERRIDE;
   virtual void SetHasError() MOZ_OVERRIDE;
 
-  virtual nsIURI* GetURI() MOZ_OVERRIDE;
+  virtual ImageURL* GetURI() MOZ_OVERRIDE;
 
 protected:
   ImageWrapper(Image* aInnerImage)
     : mInnerImage(aInnerImage)
   {
     NS_ABORT_IF_FALSE(aInnerImage, "Cannot wrap a null image");
   }
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -370,17 +370,17 @@ static nsCOMPtr<nsIThread> sScaleWorkerT
 NS_IMPL_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties)
 #else
 NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
                               imgIContainerDebug)
 #endif
 
 //******************************************************************************
 RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
-                         nsIURI* aURI /* = nullptr */) :
+                         ImageURL* aURI /* = nullptr */) :
   ImageResource(aStatusTracker, aURI), // invoke superclass's constructor
   mSize(0,0),
   mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
   mMultipartDecodedFrame(nullptr),
   mAnim(nullptr),
   mLockCount(0),
   mDecodeCount(0),
 #ifdef DEBUG
@@ -2183,16 +2183,20 @@ RasterImage::RequestDecode()
 {
   return RequestDecodeCore(SYNCHRONOUS_NOTIFY);
 }
 
 /* void startDecode() */
 NS_IMETHODIMP
 RasterImage::StartDecoding()
 {
+  if (!NS_IsMainThread()) {
+    return NS_DispatchToMainThread(
+      NS_NewRunnableMethod(this, &RasterImage::StartDecoding));
+  }
   // Here we are explicitly trading off flashing for responsiveness in the case
   // that we're redecoding an image (see bug 845147).
   return RequestDecodeCore(mHasBeenDecoded ?
     SYNCHRONOUS_NOTIFY : SYNCHRONOUS_NOTIFY_AND_SOME_DECODE);
 }
 
 bool
 RasterImage::IsDecoded()
@@ -3035,16 +3039,23 @@ RasterImage::DecodePool::Singleton()
     MOZ_ASSERT(NS_IsMainThread());
     sSingleton = new DecodePool();
     ClearOnShutdown(&sSingleton);
   }
 
   return sSingleton;
 }
 
+already_AddRefed<nsIEventTarget>
+RasterImage::DecodePool::GetEventTarget()
+{
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
+  return target.forget();
+}
+
 RasterImage::DecodePool::DecodePool()
  : mThreadPoolMutex("Thread Pool")
 {
   if (gMultithreadedDecoding) {
     mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
     if (mThreadPool) {
       mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
       uint32_t limit;
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -241,16 +241,20 @@ public:
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) MOZ_OVERRIDE;
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
                                        nsresult aStatus,
                                        bool aLastPart) MOZ_OVERRIDE;
   virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
 
+  static already_AddRefed<nsIEventTarget> GetEventTarget() {
+    return DecodePool::Singleton()->GetEventTarget();
+  }
+
   /**
    * A hint of the number of bytes of source data that the image contains. If
    * called early on, this can help reduce copying and reallocations by
    * appropriately preallocating the source data buffer.
    *
    * We take this approach rather than having the source data management code do
    * something more complicated (like chunklisting) because HTTP is by far the
    * dominant source of images, and the Content-Length header is quite reliable.
@@ -409,16 +413,24 @@ private:
      * bytes or we get the image's size.  Note that this done on a best-effort
      * basis; if the size is burried too deep in the image, we'll give up.
      *
      * @return NS_ERROR if an error is encountered, and NS_OK otherwise.  (Note
      *         that we return NS_OK even when the size was not found.)
      */
     nsresult DecodeUntilSizeAvailable(RasterImage* aImg);
 
+    /**
+     * Returns an event target interface to the thread pool; primarily for
+     * OnDataAvailable delivery off main thread.
+     *
+     * @return An nsIEventTarget interface to mThreadPool.
+     */
+    already_AddRefed<nsIEventTarget> GetEventTarget();
+
     virtual ~DecodePool();
 
   private: /* statics */
     static StaticRefPtr<DecodePool> sSingleton;
 
   private: /* methods */
     DecodePool();
 
@@ -727,17 +739,18 @@ private: // data
 
   // Helpers
   bool CanDiscard();
   bool CanForciblyDiscard();
   bool DiscardingActive();
   bool StoringSourceData() const;
 
 protected:
-  RasterImage(imgStatusTracker* aStatusTracker = nullptr, nsIURI* aURI = nullptr);
+  RasterImage(imgStatusTracker* aStatusTracker = nullptr,
+              ImageURL* aURI = nullptr);
 
   bool ShouldAnimate();
 
   friend class ImageFactory;
 };
 
 inline NS_IMETHODIMP RasterImage::GetAnimationMode(uint16_t *aAnimationMode) {
   return GetAnimationModeInternal(aAnimationMode);
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -297,17 +297,17 @@ NS_IMPL_ISUPPORTS3(VectorImage,
                    imgIContainer,
                    nsIStreamListener,
                    nsIRequestObserver)
 
 //------------------------------------------------------------------------------
 // Constructor / Destructor
 
 VectorImage::VectorImage(imgStatusTracker* aStatusTracker,
-                         nsIURI* aURI /* = nullptr */) :
+                         ImageURL* aURI /* = nullptr */) :
   ImageResource(aStatusTracker, aURI), // invoke superclass's constructor
   mIsInitialized(false),
   mIsFullyLoaded(false),
   mIsDrawing(false),
   mHaveAnimations(false),
   mHasPendingInvalidation(false)
 {
 }
--- a/image/src/VectorImage.h
+++ b/image/src/VectorImage.h
@@ -70,17 +70,18 @@ public:
   // Callback for SVGParseCompleteListener.
   void OnSVGDocumentParsed();
 
   // Callbacks for SVGLoadEventListener.
   void OnSVGDocumentLoaded();
   void OnSVGDocumentError();
 
 protected:
-  VectorImage(imgStatusTracker* aStatusTracker = nullptr, nsIURI* aURI = nullptr);
+  VectorImage(imgStatusTracker* aStatusTracker = nullptr,
+              ImageURL* aURI = nullptr);
 
   virtual nsresult StartAnimation();
   virtual nsresult StopAnimation();
   virtual bool     ShouldAnimate();
 
 private:
   void CancelAllListeners();
 
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ClearOnShutdown.h"
 
 #include "ImageLogging.h"
+#include "nsPrintfCString.h"
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
 
 #include "nsCOMPtr.h"
 
 #include "nsContentUtils.h"
 #include "nsCrossSiteListenerProxy.h"
 #include "nsNetUtil.h"
@@ -509,26 +510,26 @@ void imgCacheEntry::Touch(bool updateTim
   UpdateCache();
 }
 
 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;
+    nsRefPtr<ImageURL> uri;
     mRequest->GetURI(getter_AddRefs(uri));
     mLoader->CacheEntriesChanged(uri, diff);
   }
 }
 
 void imgCacheEntry::SetHasNoProxies(bool hasNoProxies)
 {
 #if defined(PR_LOGGING)
-  nsCOMPtr<nsIURI> uri;
+  nsRefPtr<ImageURL> uri;
   mRequest->GetURI(getter_AddRefs(uri));
   nsAutoCString spec;
   if (uri)
     uri->GetSpec(spec);
   if (hasNoProxies)
     LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies true", "uri", spec.get());
   else
     LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheEntry::SetHasNoProxies false", "uri", spec.get());
@@ -642,17 +643,17 @@ nsresult imgLoader::CreateNewProxyForReq
   imgRequestProxy *proxyRequest = new imgRequestProxy();
   NS_ADDREF(proxyRequest);
 
   /* It is important to call |SetLoadFlags()| before calling |Init()| because
      |Init()| adds the request to the loadgroup.
    */
   proxyRequest->SetLoadFlags(aLoadFlags);
 
-  nsCOMPtr<nsIURI> uri;
+  nsRefPtr<ImageURL> uri;
   aRequest->GetURI(getter_AddRefs(uri));
 
   // init adds itself to imgRequest's list of observers
   nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver);
   if (NS_FAILED(rv)) {
     NS_RELEASE(proxyRequest);
     return rv;
   }
@@ -700,17 +701,17 @@ void imgCacheExpirationTracker::NotifyEx
 {
   // Hold on to a reference to this entry, because the expiration tracker
   // mechanism doesn't.
   nsRefPtr<imgCacheEntry> kungFuDeathGrip(entry);
 
 #if defined(PR_LOGGING)
   nsRefPtr<imgRequest> req(entry->GetRequest());
   if (req) {
-    nsCOMPtr<nsIURI> uri;
+    nsRefPtr<ImageURL> uri;
     req->GetURI(getter_AddRefs(uri));
     nsAutoCString spec;
     uri->GetSpec(spec);
     LOG_FUNC_WITH_PARAM(GetImgLog(), "imgCacheExpirationTracker::NotifyExpired", "entry", spec.get());
   }
 #endif
 
   // We can be called multiple times on the same entry. Don't do work multiple
@@ -771,32 +772,43 @@ void imgLoader::VerifyCacheSizes()
     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)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
   bool chrome = false;
   aURI->SchemeIs("chrome", &chrome);
-  if (chrome)
-    return mChromeCache;
-  else
-    return mCache;
+  return chrome ? mChromeCache : mCache;
 }
 
 imgCacheQueue & imgLoader::GetCacheQueue(nsIURI *aURI)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
   bool chrome = false;
   aURI->SchemeIs("chrome", &chrome);
-  if (chrome)
-    return mChromeCacheQueue;
-  else
-    return mCacheQueue;
+  return chrome ? mChromeCacheQueue : mCacheQueue;
+
+}
+
+imgLoader::imgCacheTable & imgLoader::GetCache(ImageURL *aURI)
+{
+  bool chrome = false;
+  aURI->SchemeIs("chrome", &chrome);
+  return chrome ? mChromeCache : mCache;
+}
+
+imgCacheQueue & imgLoader::GetCacheQueue(ImageURL *aURI)
+{
+  bool chrome = false;
+  aURI->SchemeIs("chrome", &chrome);
+  return chrome ? mChromeCacheQueue : mCacheQueue;
 }
 
 void imgLoader::GlobalInit()
 {
   gCacheObserver = new imgCacheObserver();
   NS_ADDREF(gCacheObserver);
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
@@ -1013,17 +1025,17 @@ bool imgLoader::PutIntoCache(nsIURI *key
   }
 
   nsRefPtr<imgRequest> request = entry->GetRequest();
   request->SetIsInCache(true);
 
   return true;
 }
 
-bool imgLoader::SetHasNoProxies(nsIURI *key, imgCacheEntry *entry)
+bool imgLoader::SetHasNoProxies(ImageURL *key, imgCacheEntry *entry)
 {
 #if defined(PR_LOGGING)
   nsAutoCString spec;
   key->GetSpec(spec);
 
   LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::SetHasNoProxies", "uri", spec.get());
 #endif
 
@@ -1043,17 +1055,17 @@ bool imgLoader::SetHasNoProxies(nsIURI *
   }
 
   imgCacheTable &cache = GetCache(key);
   CheckCacheLimits(cache, queue);
 
   return true;
 }
 
-bool imgLoader::SetHasProxies(nsIURI *key)
+bool imgLoader::SetHasProxies(ImageURL *key)
 {
   VerifyCacheSizes();
 
   imgCacheTable &cache = GetCache(key);
 
   nsAutoCString spec;
   key->GetSpec(spec);
 
@@ -1070,17 +1082,17 @@ bool imgLoader::SetHasProxies(nsIURI *ke
     entry->SetHasNoProxies(false);
 
     return true;
   }
 
   return false;
 }
 
-void imgLoader::CacheEntriesChanged(nsIURI *uri, int32_t sizediff /* = 0 */)
+void imgLoader::CacheEntriesChanged(ImageURL *uri, int32_t sizediff /* = 0 */)
 {
   imgCacheQueue &queue = GetCacheQueue(uri);
   queue.MarkDirty();
   queue.UpdateSize(sizediff);
 }
 
 void imgLoader::CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue)
 {
@@ -1093,17 +1105,17 @@ void imgLoader::CheckCacheLimits(imgCach
     // Remove the first entry in the queue.
     nsRefPtr<imgCacheEntry> entry(queue.Pop());
 
     NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
 
 #if defined(PR_LOGGING)
     nsRefPtr<imgRequest> req(entry->GetRequest());
     if (req) {
-      nsCOMPtr<nsIURI> uri;
+      nsRefPtr<ImageURL> uri;
       req->GetURI(getter_AddRefs(uri));
       nsAutoCString spec;
       uri->GetSpec(spec);
       LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::CheckCacheLimits", "entry", spec.get());
     }
 #endif
 
     if (entry)
@@ -1185,27 +1197,30 @@ bool imgLoader::ValidateRequestWithNewCh
     nsRefPtr<nsProgressNotificationProxy> progressproxy =
         new nsProgressNotificationProxy(newChannel, req);
     if (!progressproxy)
       return false;
 
     nsRefPtr<imgCacheValidator> hvc =
       new imgCacheValidator(progressproxy, this, request, aCX, forcePrincipalCheck);
 
-    nsCOMPtr<nsIStreamListener> listener = hvc.get();
+    // Casting needed here to get past multiple inheritance.
+    nsCOMPtr<nsIStreamListener> listener =
+      do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
+    NS_ENSURE_TRUE(listener, false);
 
     // We must set the notification callbacks before setting up the
     // CORS listener, because that's also interested inthe
     // notification callbacks.
     newChannel->SetNotificationCallbacks(hvc);
 
     if (aCORSMode != imgIRequest::CORS_NONE) {
       bool withCredentials = aCORSMode == imgIRequest::CORS_USE_CREDENTIALS;
       nsRefPtr<nsCORSListenerProxy> corsproxy =
-        new nsCORSListenerProxy(hvc, aLoadingPrincipal, withCredentials);
+        new nsCORSListenerProxy(listener, aLoadingPrincipal, withCredentials);
       rv = corsproxy->Init(newChannel);
       if (NS_FAILED(rv)) {
         return false;
       }
 
       listener = corsproxy;
     }
 
@@ -1348,27 +1363,48 @@ bool imgLoader::ValidateEntry(imgCacheEn
                                          aReferrerURI, aLoadGroup, aObserver,
                                          aCX, aLoadFlags, aProxyRequest, aPolicy,
                                          aLoadingPrincipal, aCORSMode);
   }
 
   return !validateRequest;
 }
 
-
 bool imgLoader::RemoveFromCache(nsIURI *aKey)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
+
+  if (!aKey) return false;
+
+  imgCacheTable &cache = GetCache(aKey);
+  imgCacheQueue &queue = GetCacheQueue(aKey);
+
+  nsAutoCString spec;
+  aKey->GetSpec(spec);
+
+  return RemoveFromCache(spec, cache, queue);
+}
+
+bool imgLoader::RemoveFromCache(ImageURL *aKey)
+{
   if (!aKey) return false;
 
   imgCacheTable &cache = GetCache(aKey);
   imgCacheQueue &queue = GetCacheQueue(aKey);
 
   nsAutoCString spec;
   aKey->GetSpec(spec);
 
+  return RemoveFromCache(spec, cache, queue);
+}
+
+bool imgLoader::RemoveFromCache(nsCString& spec,
+                                imgCacheTable &cache,
+                                imgCacheQueue &queue)
+{
   LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "uri", spec.get());
 
   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!");
 
@@ -1391,17 +1427,17 @@ bool imgLoader::RemoveFromCache(nsIURI *
 }
 
 bool imgLoader::RemoveFromCache(imgCacheEntry *entry)
 {
   LOG_STATIC_FUNC(GetImgLog(), "imgLoader::RemoveFromCache entry");
 
   nsRefPtr<imgRequest> request = entry->GetRequest();
   if (request) {
-    nsCOMPtr<nsIURI> key;
+    nsRefPtr<ImageURL> key;
     if (NS_SUCCEEDED(request->GetURI(getter_AddRefs(key))) && key) {
       imgCacheTable &cache = GetCache(key);
       imgCacheQueue &queue = GetCacheQueue(key);
       nsAutoCString spec;
       key->GetSpec(spec);
 
       LOG_STATIC_FUNC_WITH_PARAM(GetImgLog(), "imgLoader::RemoveFromCache", "entry's uri", spec.get());
 
@@ -1996,17 +2032,20 @@ nsresult imgLoader::GetMimeTypeFromConte
 
 /**
  * proxy stream listener class used to handle multipart/x-mixed-replace
  */
 
 #include "nsIRequest.h"
 #include "nsIStreamConverterService.h"
 
-NS_IMPL_ISUPPORTS2(ProxyListener, nsIStreamListener, nsIRequestObserver)
+NS_IMPL_ISUPPORTS3(ProxyListener,
+                   nsIStreamListener,
+                   nsIThreadRetargetableStreamListener,
+                   nsIRequestObserver)
 
 ProxyListener::ProxyListener(nsIStreamListener *dest) :
   mDestListener(dest)
 {
   /* member initializers and constructor code */
 }
 
 ProxyListener::~ProxyListener()
@@ -2073,21 +2112,40 @@ ProxyListener::OnDataAvailable(nsIReques
                                uint32_t count)
 {
   if (!mDestListener)
     return NS_ERROR_FAILURE;
 
   return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
 }
 
+/** nsThreadRetargetableStreamListener methods **/
+NS_IMETHODIMP
+ProxyListener::CheckListenerChain()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+    do_QueryInterface(mDestListener, &rv);
+  if (retargetableListener) {
+    rv = retargetableListener->CheckListenerChain();
+  }
+  PR_LOG(GetImgLog(), PR_LOG_DEBUG,
+         ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%x]",
+          (NS_SUCCEEDED(rv) ? "success" : "failure"),
+          this, (nsIStreamListener*)mDestListener, rv));
+  return rv;
+}
+
 /**
  * http validate class.  check a channel for a 304
  */
 
-NS_IMPL_ISUPPORTS5(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
+NS_IMPL_ISUPPORTS6(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
+                   nsIThreadRetargetableStreamListener,
                    nsIChannelEventSink, nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
                                      imgLoader* loader, imgRequest *request,
                                      void *aContext, bool forcePrincipalCheckForCacheEntry)
  : mProgressProxy(progress),
    mRequest(request),
@@ -2164,17 +2222,21 @@ NS_IMETHODIMP imgCacheValidator::OnStart
 
       return NS_OK;
     }
   }
 
   // We can't load out of cache. We have to create a whole new request for the
   // data that's coming in off the channel.
   nsCOMPtr<nsIURI> uri;
-  mRequest->GetURI(getter_AddRefs(uri));
+  {
+    nsRefPtr<ImageURL> imageURL;
+    mRequest->GetURI(getter_AddRefs(imageURL));
+    uri = imageURL->ToIURI();
+  }
 
 #if defined(PR_LOGGING)
   nsAutoCString spec;
   uri->GetSpec(spec);
   LOG_MSG_WITH_PARAM(GetImgLog(), "imgCacheValidator::OnStartRequest creating new request", "uri", spec.get());
 #endif
 
   int32_t corsmode = mRequest->GetCORSMode();
@@ -2240,16 +2302,34 @@ imgCacheValidator::OnDataAvailable(nsIRe
     uint32_t _retval;
     inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
     return NS_OK;
   }
 
   return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
 }
 
+/** nsIThreadRetargetableStreamListener methods **/
+
+NS_IMETHODIMP
+imgCacheValidator::CheckListenerChain()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+    do_QueryInterface(mDestListener, &rv);
+  if (retargetableListener) {
+    rv = retargetableListener->CheckListenerChain();
+  }
+  PR_LOG(GetImgLog(), PR_LOG_DEBUG,
+         ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %d=%s",
+          this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv));
+  return rv;
+}
+
 /** nsIInterfaceRequestor methods **/
 
 NS_IMETHODIMP imgCacheValidator::GetInterface(const nsIID & aIID, void **aResult)
 {
   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)))
     return QueryInterface(aIID, aResult);
 
   return mProgressProxy->GetInterface(aIID, aResult);
--- a/image/src/imgLoader.h
+++ b/image/src/imgLoader.h
@@ -14,26 +14,33 @@
 #include "nsWeakReference.h"
 #include "nsIContentSniffer.h"
 #include "nsRefPtrHashtable.h"
 #include "nsExpirationTracker.h"
 #include "nsAutoPtr.h"
 #include "imgRequest.h"
 #include "nsIProgressEventSink.h"
 #include "nsIChannel.h"
+#include "nsIThreadRetargetableStreamListener.h"
 #include "imgIRequest.h"
 
 class imgLoader;
 class imgRequestProxy;
 class imgINotificationObserver;
 class nsILoadGroup;
 class imgCacheExpirationTracker;
 class imgMemoryReporter;
 class nsIChannelPolicy;
 
+namespace mozilla {
+namespace image {
+class ImageURL;
+}
+}
+
 class imgCacheEntry
 {
 public:
   imgCacheEntry(imgLoader* loader, imgRequest *request, bool aForcePrincipalCheck);
   ~imgCacheEntry();
 
   nsrefcnt AddRef()
   {
@@ -200,16 +207,19 @@ private:
 
 class imgLoader : public imgILoader,
                   public nsIContentSniffer,
                   public imgICache,
                   public nsSupportsWeakReference,
                   public nsIObserver
 {
 public:
+  typedef mozilla::image::ImageURL ImageURL;
+  typedef nsRefPtrHashtable<nsCStringHashKey, imgCacheEntry> imgCacheTable;
+
   NS_DECL_ISUPPORTS
   NS_DECL_IMGILOADER
   NS_DECL_NSICONTENTSNIFFER
   NS_DECL_IMGICACHE
   NS_DECL_NSIOBSERVER
 
   imgLoader();
   virtual ~imgLoader();
@@ -258,16 +268,20 @@ public:
 
   nsresult ClearChromeImageCache();
   nsresult ClearImageCache();
   void MinimizeCaches();
 
   nsresult InitCache();
 
   bool RemoveFromCache(nsIURI *aKey);
+  bool RemoveFromCache(ImageURL *aKey);
+  bool RemoveFromCache(nsCString &spec,
+                       imgCacheTable &cache,
+                       imgCacheQueue &queue);
   bool RemoveFromCache(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,
@@ -299,18 +313,18 @@ public:
   //
   // 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.
-  bool SetHasNoProxies(nsIURI *key, imgCacheEntry *entry);
-  bool SetHasProxies(nsIURI *key);
+  bool SetHasNoProxies(ImageURL *key, imgCacheEntry *entry);
+  bool SetHasProxies(ImageURL *key);
 
 private: // methods
 
   bool ValidateEntry(imgCacheEntry *aEntry, nsIURI *aKey,
                        nsIURI *aInitialDocumentURI, nsIURI *aReferrerURI,
                        nsILoadGroup *aLoadGroup,
                        imgINotificationObserver *aObserver, nsISupports *aCX,
                        nsLoadFlags aLoadFlags, bool aCanMakeNewChannel,
@@ -331,25 +345,24 @@ private: // methods
                                        int32_t aCORSMode);
 
   nsresult CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup *aLoadGroup,
                                     imgINotificationObserver *aObserver,
                                     nsLoadFlags aLoadFlags, imgRequestProxy **_retval);
 
   void ReadAcceptHeaderPref();
 
-
-  typedef nsRefPtrHashtable<nsCStringHashKey, imgCacheEntry> imgCacheTable;
-
   nsresult EvictEntries(imgCacheTable &aCacheToClear);
   nsresult EvictEntries(imgCacheQueue &aQueueToClear);
 
   imgCacheTable &GetCache(nsIURI *aURI);
   imgCacheQueue &GetCacheQueue(nsIURI *aURI);
-  void CacheEntriesChanged(nsIURI *aURI, int32_t sizediff = 0);
+  imgCacheTable &GetCache(ImageURL *aURI);
+  imgCacheQueue &GetCacheQueue(ImageURL *aURI);
+  void CacheEntriesChanged(ImageURL *aURI, int32_t sizediff = 0);
   void CheckCacheLimits(imgCacheTable &cache, imgCacheQueue &queue);
 
 private: // data
   friend class imgCacheEntry;
   friend class imgMemoryReporter;
 
   imgCacheTable mCache;
   imgCacheQueue mCacheQueue;
@@ -370,26 +383,29 @@ private: // data
 
 
 /**
  * proxy stream listener class used to handle multipart/x-mixed-replace
  */
 
 #include "nsCOMPtr.h"
 #include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
 
 class ProxyListener : public nsIStreamListener
+                    , public nsIThreadRetargetableStreamListener
 {
 public:
   ProxyListener(nsIStreamListener *dest);
   virtual ~ProxyListener();
 
   /* additional members */
   NS_DECL_ISUPPORTS
   NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
 
 private:
   nsCOMPtr<nsIStreamListener> mDestListener;
 };
 
 /**
  * A class that implements nsIProgressEventSink and forwards all calls to it to
@@ -422,28 +438,30 @@ class nsProgressNotificationProxy MOZ_FI
 
 /**
  * validate checker
  */
 
 #include "nsCOMArray.h"
 
 class imgCacheValidator : public nsIStreamListener,
+                          public nsIThreadRetargetableStreamListener,
                           public nsIChannelEventSink,
                           public nsIInterfaceRequestor,
                           public nsIAsyncVerifyRedirectCallback
 {
 public:
   imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader,
                     imgRequest *request, void *aContext, bool forcePrincipalCheckForCacheEntry);
   virtual ~imgCacheValidator();
 
   void AddProxy(imgRequestProxy *aProxy);
 
   NS_DECL_ISUPPORTS
+  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
 private:
   nsCOMPtr<nsIStreamListener> mDestListener;
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -7,19 +7,21 @@
 #include "imgRequest.h"
 #include "ImageLogging.h"
 
 #include "imgLoader.h"
 #include "imgRequestProxy.h"
 #include "imgStatusTracker.h"
 #include "ImageFactory.h"
 #include "Image.h"
+#include "RasterImage.h"
 
 #include "nsIChannel.h"
 #include "nsICachingChannel.h"
+#include "nsIThreadRetargetableRequest.h"
 #include "nsIInputStream.h"
 #include "nsIMultiPartChannel.h"
 #include "nsIHttpChannel.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsMimeTypes.h"
 
 #include "nsIInterfaceRequestorUtils.h"
@@ -42,18 +44,19 @@ GetImgLog()
 {
   static PRLogModuleInfo *sImgLog;
   if (!sImgLog)
     sImgLog = PR_NewLogModule("imgRequest");
   return sImgLog;
 }
 #endif
 
-NS_IMPL_ISUPPORTS5(imgRequest,
+NS_IMPL_ISUPPORTS6(imgRequest,
                    nsIStreamListener, nsIRequestObserver,
+                   nsIThreadRetargetableStreamListener,
                    nsIChannelEventSink,
                    nsIInterfaceRequestor,
                    nsIAsyncVerifyRedirectCallback)
 
 imgRequest::imgRequest(imgLoader* aLoader)
  : mLoader(aLoader)
  , mStatusTracker(new imgStatusTracker(nullptr))
  , mValidator(nullptr)
@@ -80,27 +83,30 @@ nsresult imgRequest::Init(nsIURI *aURI,
                           nsIURI *aCurrentURI,
                           nsIRequest *aRequest,
                           nsIChannel *aChannel,
                           imgCacheEntry *aCacheEntry,
                           void *aLoadId,
                           nsIPrincipal* aLoadingPrincipal,
                           int32_t aCORSMode)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
+
   LOG_FUNC(GetImgLog(), "imgRequest::Init");
 
   NS_ABORT_IF_FALSE(!mImage, "Multiple calls to init");
   NS_ABORT_IF_FALSE(aURI, "No uri");
   NS_ABORT_IF_FALSE(aCurrentURI, "No current uri");
   NS_ABORT_IF_FALSE(aRequest, "No request");
   NS_ABORT_IF_FALSE(aChannel, "No channel");
 
   mProperties = do_CreateInstance("@mozilla.org/properties;1");
 
-  mURI = aURI;
+  // Use ImageURL to ensure access to URI data off main thread.
+  mURI = new ImageURL(aURI);
   mCurrentURI = aCurrentURI;
   mRequest = aRequest;
   mChannel = aChannel;
   mTimedChannel = do_QueryInterface(mChannel);
 
   mLoadingPrincipal = aLoadingPrincipal;
   mCORSMode = aCORSMode;
 
@@ -243,24 +249,31 @@ void imgRequest::Cancel(nsresult aStatus
   LOG_SCOPE(GetImgLog(), "imgRequest::Cancel");
 
   imgStatusTracker& statusTracker = GetStatusTracker();
 
   statusTracker.MaybeUnblockOnload();
 
   statusTracker.RecordCancel();
 
-  RemoveFromCache();
+  if (NS_IsMainThread()) {
+    RemoveFromCache();
+  } else {
+    NS_DispatchToMainThread(
+      NS_NewRunnableMethod(this, &imgRequest::RemoveFromCache));
+  }
 
   if (mRequest && statusTracker.IsLoading())
     mRequest->Cancel(aStatus);
 }
 
-nsresult imgRequest::GetURI(nsIURI **aURI)
+nsresult imgRequest::GetURI(ImageURL **aURI)
 {
+  MOZ_ASSERT(aURI);
+
   LOG_FUNC(GetImgLog(), "imgRequest::GetURI");
 
   if (mURI) {
     *aURI = mURI;
     NS_ADDREF(*aURI);
     return NS_OK;
   }
 
@@ -565,16 +578,36 @@ NS_IMETHODIMP imgRequest::OnStartRequest
 
   mApplicationCache = GetApplicationCache(aRequest);
 
   // Shouldn't we be dead already if this gets hit?  Probably multipart/x-mixed-replace...
   if (GetStatusTracker().ConsumerCount() == 0) {
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
   }
 
+  // Try to retarget OnDataAvailable to a decode thread.
+  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
+  nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
+    do_QueryInterface(aRequest);
+  if (httpChannel && retargetable &&
+      !(mIsMultiPartChannel && mImage)) {
+    nsAutoCString mimeType;
+    nsresult rv = httpChannel->GetContentType(mimeType);
+    if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) {
+      // Image object not created until OnDataAvailable, so forward to static
+      // DecodePool directly.
+      nsCOMPtr<nsIEventTarget> target = RasterImage::GetEventTarget();
+      rv = retargetable->RetargetDeliveryTo(target);
+    }
+    PR_LOG(GetImgLog(), PR_LOG_WARNING,
+           ("[this=%p] imgRequest::OnStartRequest -- "
+            "RetargetDeliveryTo rv %d=%s\n",
+            this, NS_SUCCEEDED(rv) ? "succeeded" : "failed", rv));
+  }
+
   return NS_OK;
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
 {
   LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest");
 
@@ -639,16 +672,25 @@ struct mimetype_closure
 {
   nsACString* newType;
 };
 
 /* prototype for these defined below */
 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in, void* closure, const char* fromRawSegment,
                                          uint32_t toOffset, uint32_t count, uint32_t *writeCount);
 
+/** nsThreadRetargetableStreamListener methods **/
+NS_IMETHODIMP
+imgRequest::CheckListenerChain()
+{
+  // TODO Might need more checking here.
+  NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
+  return NS_OK;
+}
+
 /** nsIStreamListener methods **/
 
 /* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
 NS_IMETHODIMP
 imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
                             nsIInputStream *inStr, uint64_t sourceOffset,
                             uint32_t count)
 {
@@ -715,38 +757,24 @@ imgRequest::OnDataAvailable(nsIRequest *
       if (resniffMimeType) {
         NS_ABORT_IF_FALSE(mIsMultiPartChannel, "Resniffing a non-multipart image");
 
         imgStatusTracker* freshTracker = new imgStatusTracker(nullptr);
         freshTracker->AdoptConsumers(&GetStatusTracker());
         mStatusTracker = freshTracker;
       }
 
-      /* set our mimetype as a property */
-      nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
-      if (contentType) {
-        contentType->SetData(mContentType);
-        mProperties->Set("type", contentType);
-      }
-
-      /* set our content disposition as a property */
-      nsAutoCString disposition;
-      if (chan) {
-        chan->GetContentDispositionHeader(disposition);
-      }
-      if (!disposition.IsEmpty()) {
-        nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
-        if (contentDisposition) {
-          contentDisposition->SetData(disposition);
-          mProperties->Set("content-disposition", contentDisposition);
-        }
-      }
+      SetProperties(chan);
 
       LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
 
+      // XXX If server lied about mimetype and it's SVG, we may need to copy
+      // the data and dispatch back to the main thread, AND tell the channel to
+      // dispatch there in the future.
+
       // Now we can create a new image to hold the data. If we don't have a decoder
       // for this mimetype we'll find out about it here.
       mImage = ImageFactory::CreateImage(aRequest, mStatusTracker, mContentType,
                                          mURI, mIsMultiPartChannel,
                                          static_cast<uint32_t>(mInnerWindowId));
 
       // Release our copy of the status tracker since the image owns it now.
       mStatusTracker = nullptr;
@@ -780,16 +808,68 @@ imgRequest::OnDataAvailable(nsIRequest *
             "copy to RasterImage failed\n", this));
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
     return NS_BINDING_ABORTED;
   }
 
   return NS_OK;
 }
 
+class SetPropertiesEvent : public nsRunnable
+{
+public:
+  SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan)
+    : mImgRequest(aImgRequest)
+    , mChan(aChan)
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
+    MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null");
+  }
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only");
+    MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null");
+    mImgRequest->SetProperties(mChan);
+    return NS_OK;
+  }
+private:
+  nsRefPtr<imgRequest> mImgRequest;
+  nsCOMPtr<nsIChannel> mChan;
+};
+
+void
+imgRequest::SetProperties(nsIChannel* aChan)
+{
+  // Force execution on main thread since some property objects are non
+  // threadsafe.
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan));
+    return;
+  }
+  /* set our mimetype as a property */
+  nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+  if (contentType) {
+    contentType->SetData(mContentType);
+    mProperties->Set("type", contentType);
+  }
+
+  /* set our content disposition as a property */
+  nsAutoCString disposition;
+  if (aChan) {
+    aChan->GetContentDispositionHeader(disposition);
+  }
+  if (!disposition.IsEmpty()) {
+    nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+    if (contentDisposition) {
+      contentDisposition->SetData(disposition);
+      mProperties->Set("content-disposition", contentDisposition);
+    }
+  }
+}
+
 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in,
                                          void* data,
                                          const char* fromRawSegment,
                                          uint32_t toOffset,
                                          uint32_t count,
                                          uint32_t *writeCount)
 {
   mimetype_closure* closure = static_cast<mimetype_closure*>(data);
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -5,20 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef imgRequest_h__
 #define imgRequest_h__
 
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
 #include "nsIPrincipal.h"
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
+#include "nsProxyRelease.h"
 #include "nsStringGlue.h"
 #include "nsError.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 
 class imgCacheValidator;
 class imgStatusTracker;
 class imgLoader;
 class imgRequestProxy;
@@ -29,29 +31,32 @@ class nsIApplicationCache;
 class nsIProperties;
 class nsIRequest;
 class nsITimedChannel;
 class nsIURI;
 
 namespace mozilla {
 namespace image {
 class Image;
+class ImageURL;
 } // namespace image
 } // namespace mozilla
 
 class imgRequest : public nsIStreamListener,
+                   public nsIThreadRetargetableStreamListener,
                    public nsIChannelEventSink,
                    public nsIInterfaceRequestor,
                    public nsIAsyncVerifyRedirectCallback
 {
 public:
+  typedef mozilla::image::ImageURL ImageURL;
   imgRequest(imgLoader* aLoader);
   virtual ~imgRequest();
 
-  NS_DECL_ISUPPORTS
+  NS_DECL_THREADSAFE_ISUPPORTS
 
   nsresult Init(nsIURI *aURI,
                 nsIURI *aCurrentURI,
                 nsIRequest *aRequest,
                 nsIChannel *aChannel,
                 imgCacheEntry *aCacheEntry,
                 void *aLoadId,
                 nsIPrincipal* aLoadingPrincipal,
@@ -116,17 +121,18 @@ public:
   inline nsIPrincipal* GetPrincipal() const { return mPrincipal.get(); }
 
   // Resize the cache entry to 0 if it exists
   void ResetCacheEntry();
 
   // Update the cache entry size based on the image container
   void UpdateCacheEntrySize();
 
-  nsresult GetURI(nsIURI **aURI);
+  // OK to use on any thread.
+  nsresult GetURI(ImageURL **aURI);
 
 private:
   friend class imgCacheEntry;
   friend class imgRequestProxy;
   friend class imgLoader;
   friend class imgCacheValidator;
   friend class imgStatusTracker;
   friend class imgCacheExpirationTracker;
@@ -171,30 +177,35 @@ private:
   // try to update or modify the image cache.
   void SetIsInCache(bool cacheable);
 
   bool IsBlockingOnload() const;
   void SetBlockingOnload(bool block) const;
 
 public:
   NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
+  // Sets properties for this image; will dispatch to main thread if needed.
+  void SetProperties(nsIChannel* aChan);
+
 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;
+  // keyed on in the cache. We store a string here to avoid off main thread
+  // refcounting issues with nsStandardURL.
+  nsRefPtr<ImageURL> 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;
   // The principal of this image.
   nsCOMPtr<nsIPrincipal> mPrincipal;
   // Status-tracker -- transferred to mImage, when it gets instantiated
   nsRefPtr<imgStatusTracker> mStatusTracker;
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -8,16 +8,17 @@
 #include "imgIOnloadBlocker.h"
 
 #include "Image.h"
 #include "ImageOps.h"
 #include "nsError.h"
 #include "ImageLogging.h"
 #include "nsCRTGlue.h"
 #include "imgINotificationObserver.h"
+#include "nsNetUtil.h"
 
 using namespace mozilla::image;
 
 // The split of imgRequestProxy and imgRequestProxyStatic means that
 // certain overridden functions need to be usable in the destructor.
 // Since virtual functions can't be used in that way, this class
 // provides a behavioural trait for each class to use instead.
 class ProxyBehaviour
@@ -141,17 +142,17 @@ imgRequestProxy::~imgRequestProxy()
     */
     mCanceled = true;
     GetOwner()->RemoveProxy(this, NS_OK);
   }
 }
 
 nsresult imgRequestProxy::Init(imgRequest* aOwner,
                                nsILoadGroup* aLoadGroup,
-                               nsIURI* aURI,
+                               ImageURL* aURI,
                                imgINotificationObserver* aObserver)
 {
   NS_PRECONDITION(!GetOwner() && !mListener, "imgRequestProxy is already initialized");
 
   LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequestProxy::Init", "request", aOwner);
 
   NS_ABORT_IF_FALSE(mAnimationConsumers == 0, "Cannot have animation before Init");
 
@@ -499,16 +500,24 @@ NS_IMETHODIMP imgRequestProxy::GetImageS
   *aStatus = GetStatusTracker().GetImageStatus();
 
   return NS_OK;
 }
 
 /* readonly attribute nsIURI URI; */
 NS_IMETHODIMP imgRequestProxy::GetURI(nsIURI **aURI)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
+  nsCOMPtr<nsIURI> uri = mURI->ToIURI();
+  uri.forget(aURI);
+  return NS_OK;
+}
+
+nsresult imgRequestProxy::GetURI(ImageURL **aURI)
+{
   if (!mURI)
     return NS_ERROR_FAILURE;
 
   NS_ADDREF(*aURI = mURI);
 
   return NS_OK;
 }
 
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -31,40 +31,42 @@ class imgINotificationObserver;
 class imgRequestNotifyRunnable;
 class imgStatusNotifyRunnable;
 class nsIntRect;
 class ProxyBehaviour;
 
 namespace mozilla {
 namespace image {
 class Image;
+class ImageURL;
 } // namespace image
 } // namespace mozilla
 
 class imgRequestProxy : public imgIRequest,
                         public nsISupportsPriority,
                         public nsISecurityInfoProvider,
                         public nsITimedChannel
 {
 public:
+  typedef mozilla::image::ImageURL ImageURL;
   NS_DECL_ISUPPORTS
   NS_DECL_IMGIREQUEST
   NS_DECL_NSIREQUEST
   NS_DECL_NSISUPPORTSPRIORITY
   NS_DECL_NSISECURITYINFOPROVIDER
   // nsITimedChannel declared below
 
   imgRequestProxy();
   virtual ~imgRequestProxy();
 
   // Callers to Init or ChangeOwner are required to call NotifyListener after
   // (although not immediately after) doing so.
   nsresult Init(imgRequest* aOwner,
                 nsILoadGroup *aLoadGroup,
-                nsIURI* aURI,
+                ImageURL* aURI,
                 imgINotificationObserver *aObserver);
 
   nsresult ChangeOwner(imgRequest *aNewOwner); // this will change mOwner.  Do not call this if the previous
                                                // owner has already sent notifications out!
 
   void AddToLoadGroup();
   void RemoveFromLoadGroup(bool releaseLoadGroup);
 
@@ -101,16 +103,18 @@ public:
   // IncrementAnimationConsumers. This is necessary since we need
   // to do it before the proxy itself is destroyed. See
   // imgRequest::RemoveProxy
   void ClearAnimationConsumers();
 
   virtual nsresult Clone(imgINotificationObserver* aObserver, imgRequestProxy** aClone);
   nsresult GetStaticRequest(imgRequestProxy** aReturn);
 
+  nsresult GetURI(ImageURL **aURI);
+
 protected:
   friend class imgStatusTracker;
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
 
   class imgCancelRunnable;
   friend class imgCancelRunnable;
 
@@ -189,17 +193,17 @@ public:
 protected:
   nsAutoPtr<ProxyBehaviour> mBehaviour;
 
 private:
   friend class imgCacheValidator;
   friend imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis);
 
   // The URI of our request.
-  nsCOMPtr<nsIURI> mURI;
+  nsRefPtr<ImageURL> mURI;
 
   // mListener is only promised to be a weak ref (see imgILoader.idl),
   // but we actually keep a strong ref to it until we've seen our
   // first OnStopRequest.
   imgINotificationObserver* mListener;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   nsLoadFlags mLoadFlags;
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -6,16 +6,17 @@
 
 #include "imgStatusTracker.h"
 
 #include "imgIContainer.h"
 #include "imgRequestProxy.h"
 #include "imgDecoderObserver.h"
 #include "Image.h"
 #include "ImageLogging.h"
+#include "nsNetUtil.h"
 #include "nsIObserverService.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Services.h"
 
 using namespace mozilla::image;
 
 class imgStatusTrackerNotifyingObserver : public imgDecoderObserver
@@ -386,17 +387,17 @@ class imgRequestNotifyRunnable : public 
     nsTArray< nsRefPtr<imgRequestProxy> > mProxies;
 };
 
 void
 imgStatusTracker::Notify(imgRequestProxy* proxy)
 {
 #ifdef PR_LOGGING
   if (GetImage() && GetImage()->GetURI()) {
-    nsCOMPtr<nsIURI> uri(GetImage()->GetURI());
+    nsRefPtr<ImageURL> uri(GetImage()->GetURI());
     nsAutoCString spec;
     uri->GetSpec(spec);
     LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::Notify async", "uri", spec.get());
   } else {
     LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::Notify async", "uri", "<unknown>");
   }
 #endif
 
@@ -439,17 +440,17 @@ class imgStatusNotifyRunnable : public n
     nsRefPtr<Image> mImage;
     nsRefPtr<imgRequestProxy> mProxy;
 };
 
 void
 imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy)
 {
 #ifdef PR_LOGGING
-  nsCOMPtr<nsIURI> uri;
+  nsRefPtr<ImageURL> uri;
   proxy->GetURI(getter_AddRefs(uri));
   nsAutoCString spec;
   uri->GetSpec(spec);
   LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::NotifyCurrentState", "uri", spec.get());
 #endif
 
   proxy->SetNotificationsDeferred(true);
 
@@ -627,17 +628,17 @@ imgStatusTracker::CloneForRecording()
   imgStatusTracker* clone = new imgStatusTracker(*this);
   return clone;
 }
 
 void
 imgStatusTracker::SyncNotify(imgRequestProxy* proxy)
 {
 #ifdef PR_LOGGING
-  nsCOMPtr<nsIURI> uri;
+  nsRefPtr<ImageURL> uri;
   proxy->GetURI(getter_AddRefs(uri));
   nsAutoCString spec;
   uri->GetSpec(spec);
   LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgStatusTracker::SyncNotify", "uri", spec.get());
 #endif
 
   nsIntRect r;
   if (mImage) {
@@ -1066,17 +1067,22 @@ imgStatusTracker::RecordError()
 void
 imgStatusTracker::FireFailureNotification()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Some kind of problem has happened with image decoding.
   // Report the URI to net:failed-to-process-uri-conent observers.
   if (GetImage()) {
-    nsCOMPtr<nsIURI> uri = GetImage()->GetURI();
+    // Should be on main thread, so ok to create a new nsIURI.
+    nsCOMPtr<nsIURI> uri;
+    {
+      nsRefPtr<ImageURL> threadsafeUriData = GetImage()->GetURI();
+      uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr;
+    }
     if (uri) {
       nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
       if (os) {
         os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr);
       }
     }
   }
 }
--- a/netwerk/base/src/nsMediaFragmentURIParser.cpp
+++ b/netwerk/base/src/nsMediaFragmentURIParser.cpp
@@ -20,16 +20,22 @@ namespace mozilla { namespace net {
 nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsIURI* aURI)
   : mClipUnit(eClipUnit_Pixel)
 {
   nsAutoCString ref;
   aURI->GetRef(ref);
   Parse(ref);
 }
 
+nsMediaFragmentURIParser::nsMediaFragmentURIParser(nsCString& aRef)
+  : mClipUnit(eClipUnit_Pixel)
+{
+  Parse(aRef);
+}
+
 bool nsMediaFragmentURIParser::ParseNPT(nsDependentSubstring aString)
 {
   nsDependentSubstring original(aString);
   if (aString.Length() > 4 &&
       aString[0] == 'n' && aString[1] == 'p' &&
       aString[2] == 't' && aString[3] == ':') {
     aString.Rebind(aString, 4);
   }
--- a/netwerk/base/src/nsMediaFragmentURIParser.h
+++ b/netwerk/base/src/nsMediaFragmentURIParser.h
@@ -30,16 +30,19 @@ enum ClipUnit
 };
 
 class nsMediaFragmentURIParser
 {
 public:
   // Create a parser with the provided URI.
   nsMediaFragmentURIParser(nsIURI* aURI);
 
+  // Create a parser with the provided URI reference portion.
+  nsMediaFragmentURIParser(nsCString& aRef);
+
   // True if a valid temporal media fragment indicated a start time.
   bool HasStartTime() const { return !mStart.empty(); }
 
   // If a valid temporal media fragment indicated a start time, returns
   // it in units of seconds. If not, defaults to 0.
   double GetStartTime() const { return mStart.ref(); }
 
   // True if a valid temporal media fragment indicated an end time.