Bug 1530402 - Provide imgTools.decodeFromChannelAsync. r=aosmond,snorp
authorAgi Sferro <agi@sferro.dev>
Mon, 18 Nov 2019 16:48:53 +0000
changeset 502432 9afd2ad768ec8b7fde452d4c28707ef1714cf313
parent 502431 64fde5f3d49f19c9ab0d5e788f69ebc32b7d6a3f
child 502433 0fdc505ba2f610794fce1503e21f9be99f58f108
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaosmond, snorp
bugs1530402
milestone72.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 1530402 - Provide imgTools.decodeFromChannelAsync. r=aosmond,snorp This method allows consumers to decode images from a |nsIChannel| instance. This method also supports vector images (e.g. SVGs), which other decode methods don't. Differential Revision: https://phabricator.services.mozilla.com/D49037
image/imgITools.idl
image/imgTools.cpp
--- a/image/imgITools.idl
+++ b/image/imgITools.idl
@@ -1,18 +1,20 @@
 /* -*- 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/. */
 
 #include "nsISupports.idl"
 
+interface nsIChannel;
 interface nsIEventTarget;
 interface nsIInputStream;
+interface nsIURI;
 interface imgIContainer;
 interface imgILoader;
 interface imgICache;
 interface imgIScriptedNotificationObserver;
 interface imgINotificationObserver;
 interface imgIContainerCallback;
 
 webidl Document;
@@ -48,16 +50,39 @@ interface imgITools : nsISupports
      * @param aMimeType
      *        Type of image in the stream.
      */
     [implicit_jscontext]
     imgIContainer decodeImageFromArrayBuffer(in jsval aArrayBuffer,
                                              in ACString aMimeType);
 
     /**
+     * decodeImageFromChannelAsync
+     * See decodeImage. The main difference between this method and decodeImage
+     * is that here the operation is done async on a thread from the decode
+     * pool. When the operation is completed, the callback is executed with the
+     * result.
+     *
+     * @param aURI
+     *        The original URI of the image
+     * @param aChannel
+     *        Channel to the image to be decoded.
+     * @param aCallback
+     *        The callback is executed when the imgContainer is fully created.
+     * @param aObserver
+     *        Optional observer for the decoded image, the caller should make
+     *        sure the observer is kept alive as long as necessary, as ImageLib
+     *        does not keep a strong reference to the observer.
+     */
+    void decodeImageFromChannelAsync(in nsIURI aURI,
+                                     in nsIChannel aChannel,
+                                     in imgIContainerCallback aCallback,
+                                     in imgINotificationObserver aObserver);
+
+    /**
      * decodeImageAsync
      * See decodeImage. The main difference between this method and decodeImage
      * is that here the operation is done async on a thread from the decode
      * pool. When the operation is completed, the callback is executed with the
      * result.
      *
      * @param aStream
      *        An input stream for an encoded image file.
--- a/image/imgTools.cpp
+++ b/image/imgTools.cpp
@@ -18,32 +18,173 @@
 #include "imgICache.h"
 #include "imgIContainer.h"
 #include "imgIEncoder.h"
 #include "nsNetUtil.h"  // for NS_NewBufferedInputStream
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 #include "nsContentUtils.h"
 #include "nsProxyRelease.h"
+#include "nsIStreamListener.h"
 #include "ImageFactory.h"
 #include "Image.h"
+#include "IProgressObserver.h"
 #include "ScriptedNotificationObserver.h"
 #include "imgIScriptedNotificationObserver.h"
 #include "gfxPlatform.h"
 #include "js/ArrayBuffer.h"
 #include "js/RootingAPI.h"  // JS::{Handle,Rooted}
 #include "js/Value.h"       // JS::Value
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
 
 namespace {
 
+static nsresult sniff_mimetype_callback(nsIInputStream* in, void* data,
+                                        const char* fromRawSegment,
+                                        uint32_t toOffset, uint32_t count,
+                                        uint32_t* writeCount) {
+  nsCString* mimeType = static_cast<nsCString*>(data);
+  MOZ_ASSERT(mimeType, "mimeType is null!");
+
+  if (count > 0) {
+    imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *mimeType);
+  }
+
+  *writeCount = 0;
+  return NS_ERROR_FAILURE;
+}
+
+// Provides WeakPtr for imgINotificationObserver
+class NotificationObserverWrapper : public imgINotificationObserver,
+                                    public mozilla::SupportsWeakPtr<NotificationObserverWrapper> {
+ public:
+  NS_DECL_ISUPPORTS
+  NS_FORWARD_IMGINOTIFICATIONOBSERVER(mObserver->)
+  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsGeolocationRequest)
+
+  explicit NotificationObserverWrapper(imgINotificationObserver* observer) : mObserver(observer) {}
+
+ private:
+  virtual ~NotificationObserverWrapper() = default;
+  nsCOMPtr<imgINotificationObserver> mObserver;
+};
+
+NS_IMPL_ISUPPORTS(NotificationObserverWrapper, imgINotificationObserver)
+
+class ImageDecoderListener final : public nsIStreamListener,
+                                   public IProgressObserver,
+                                   public imgIContainer {
+ public:
+  NS_DECL_ISUPPORTS
+
+  ImageDecoderListener(nsIURI* aURI, imgIContainerCallback* aCallback,
+                       imgINotificationObserver* aObserver)
+      : mURI(aURI),
+        mImage(nullptr),
+        mCallback(aCallback),
+        mObserver(new NotificationObserverWrapper(aObserver)) {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_IMETHOD
+  OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+                  uint64_t aOffset, uint32_t aCount) override {
+    if (!mImage) {
+      nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+      nsCString mimeType;
+      channel->GetContentType(mimeType);
+
+      if (aInputStream) {
+        // Look at the first few bytes and see if we can tell what the data is from
+        // that since servers tend to lie. :(
+        uint32_t unused;
+        aInputStream->ReadSegments(sniff_mimetype_callback, &mimeType, aCount, &unused);
+      }
+
+      RefPtr<ProgressTracker> tracker = new ProgressTracker();
+      if (mObserver) {
+        tracker->AddObserver(this);
+      }
+
+      mImage = ImageFactory::CreateImage(channel, tracker, mimeType, mURI,
+                                         /* aIsMultiPart */ false, 0);
+
+      if (mImage->HasError()) {
+        return NS_ERROR_FAILURE;
+      }
+    }
+
+    return mImage->OnImageDataAvailable(aRequest, nullptr, aInputStream,
+                                        aOffset, aCount);
+  }
+
+  NS_IMETHOD
+  OnStartRequest(nsIRequest* aRequest) override {
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  OnStopRequest(nsIRequest* aRequest, nsresult aStatus) override {
+    // Depending on the error, we might not have received any data yet, in which case we would not
+    // have an |mImage|
+    if (mImage) {
+      mImage->OnImageDataComplete(aRequest, nullptr, aStatus, true);
+    }
+
+    nsCOMPtr<imgIContainer> container;
+    if (NS_SUCCEEDED(aStatus)) {
+      container = this;
+    }
+
+    mCallback->OnImageReady(container, aStatus);
+    return NS_OK;
+  }
+
+  virtual void Notify(int32_t aType,
+                      const nsIntRect* aRect = nullptr) override {
+    if (mObserver) {
+      mObserver->Notify(nullptr, aType, aRect);
+    }
+  }
+
+  virtual void OnLoadComplete(bool aLastPart) override {}
+
+  // Other notifications are ignored.
+  virtual void SetHasImage() override {}
+  virtual bool NotificationsDeferred() const override { return false; }
+  virtual void MarkPendingNotify() override {}
+  virtual void ClearPendingNotify() override {}
+
+  // imgIContainer
+  NS_FORWARD_IMGICONTAINER(mImage->)
+
+  nsresult GetNativeSizes(nsTArray<nsIntSize>& aNativeSizes) const override {
+    return mImage->GetNativeSizes(aNativeSizes);
+  }
+
+  size_t GetNativeSizesLength() const override {
+    return mImage->GetNativeSizesLength();
+  }
+
+ private:
+  virtual ~ImageDecoderListener() = default;
+
+  nsCOMPtr<nsIURI> mURI;
+  RefPtr<image::Image> mImage;
+  nsCOMPtr<imgIContainerCallback> mCallback;
+  WeakPtr<NotificationObserverWrapper> mObserver;
+};
+
+NS_IMPL_ISUPPORTS(ImageDecoderListener, nsIStreamListener, imgIContainer)
+
 class ImageDecoderHelper final : public Runnable,
                                  public nsIInputStreamCallback {
  public:
   NS_DECL_ISUPPORTS_INHERITED
 
   ImageDecoderHelper(already_AddRefed<image::Image> aImage,
                      already_AddRefed<nsIInputStream> aInputStream,
                      nsIEventTarget* aEventTarget,
@@ -230,16 +371,32 @@ imgTools::DecodeImageFromBuffer(const ch
   NS_ENSURE_SUCCESS(rv, rv);
 
   // All done.
   image.forget(aContainer);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+imgTools::DecodeImageFromChannelAsync(nsIURI* aURI, nsIChannel* aChannel,
+                                      imgIContainerCallback* aCallback,
+                                      imgINotificationObserver* aObserver) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_ARG_POINTER(aURI);
+  NS_ENSURE_ARG_POINTER(aChannel);
+  NS_ENSURE_ARG_POINTER(aCallback);
+
+  RefPtr<ImageDecoderListener> listener =
+      new ImageDecoderListener(aURI, aCallback, aObserver);
+
+  return aChannel->AsyncOpen(listener);
+}
+
+NS_IMETHODIMP
 imgTools::DecodeImageAsync(nsIInputStream* aInStr, const nsACString& aMimeType,
                            imgIContainerCallback* aCallback,
                            nsIEventTarget* aEventTarget) {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ENSURE_ARG_POINTER(aInStr);
   NS_ENSURE_ARG_POINTER(aCallback);
   NS_ENSURE_ARG_POINTER(aEventTarget);