Bug 1420223 - createImageBitmap must work with nsIAsyncInputStream - part 2 - imgITools::decodeImageAsync, r=aosmond
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 27 Nov 2017 17:05:57 +0100
changeset 393823 bb774ad4ae66b44e7d42fb32e8a706dac08ae64f
parent 393822 5bb11906e60260156dddd94dad2feb49a96371a6
child 393824 e257e633c87d416693404d80c080c92c91ce981d
push id32983
push userebalazs@mozilla.com
push dateMon, 27 Nov 2017 21:58:04 +0000
treeherdermozilla-central@f5f03ee9e6ab [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaosmond
bugs1420223
milestone59.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 1420223 - createImageBitmap must work with nsIAsyncInputStream - part 2 - imgITools::decodeImageAsync, r=aosmond
image/imgITools.idl
image/imgTools.cpp
--- a/image/imgITools.idl
+++ b/image/imgITools.idl
@@ -1,23 +1,25 @@
 /* -*- 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 nsIEventTarget;
 interface nsIInputStream;
 interface imgIContainer;
 interface imgILoader;
 interface imgICache;
 interface nsIDOMDocument;
 interface imgIScriptedNotificationObserver;
 interface imgINotificationObserver;
+interface imgIContainerCallback;
 
 [scriptable, builtinclass, uuid(4c2383a4-931c-484d-8c4a-973590f66e3f)]
 interface imgITools : nsISupports
 {
     /**
      * decodeImage
      * Caller provides an input stream and mimetype. We read from the stream
      * and decompress it (according to the specified mime type) and return
@@ -27,16 +29,37 @@ interface imgITools : nsISupports
      *        An input stream for an encoded image file.
      * @param aMimeType
      *        Type of image in the stream.
      */
     imgIContainer decodeImage(in nsIInputStream aStream,
                               in ACString aMimeType);
 
     /**
+     * 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.
+     * @param aMimeType
+     *        Type of image in the stream.
+     * @param aCallback
+     *        The callback is executed when the imgContainer is fully created.
+     * @param aEventTarget
+     *        This eventTarget is used to execute aCallback
+     */
+    void decodeImageAsync(in nsIInputStream aStream,
+                          in ACString aMimeType,
+                          in imgIContainerCallback aCallback,
+                          in nsIEventTarget aEventTarget);
+
+    /**
      * encodeImage
      * Caller provides an image container, and the mime type it should be
      * encoded to. We return an input stream for the encoded image data.
      *
      * @param aContainer
      *        An image container.
      * @param aMimeType
      *        Type of encoded image desired (eg "image/png").
@@ -123,8 +146,18 @@ interface imgITools : nsISupports
      * Create a wrapper around a scripted notification observer (ordinarily
      * imgINotificationObserver cannot be implemented from scripts).
      *
      * @param aObserver The scripted observer to wrap
      */
     imgINotificationObserver
     createScriptedObserver(in imgIScriptedNotificationObserver aObserver);
 };
+
+/**
+ * This is a companion interface for nsIAsyncInputStream::asyncWait.
+ */
+[function, scriptable, uuid(f195772c-a4c0-47ae-80ca-211e001c67be)]
+interface imgIContainerCallback : nsISupports
+{
+    /* If the operation fails, aStatus will contain the error value */
+    void onImageReady(in imgIContainer aImage, in nsresult aStatus);
+};
--- a/image/imgTools.cpp
+++ b/image/imgTools.cpp
@@ -1,40 +1,173 @@
 /* -*- 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 "imgTools.h"
 
+#include "DecodePool.h"
 #include "gfxUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsError.h"
 #include "imgLoader.h"
 #include "imgICache.h"
 #include "imgIContainer.h"
 #include "imgIEncoder.h"
 #include "nsStreamUtils.h"
 #include "nsContentUtils.h"
+#include "nsProxyRelease.h"
 #include "ImageFactory.h"
 #include "Image.h"
 #include "ScriptedNotificationObserver.h"
 #include "imgIScriptedNotificationObserver.h"
 #include "gfxPlatform.h"
 
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace image {
+
+namespace {
+
+class ImageDecoderHelper final : public Runnable
+                               , public nsIInputStreamCallback
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  ImageDecoderHelper(already_AddRefed<image::Image> aImage,
+                     already_AddRefed<nsIInputStream> aInputStream,
+                     nsIEventTarget* aEventTarget,
+                     imgIContainerCallback* aCallback,
+                     nsIEventTarget* aCallbackEventTarget)
+    : Runnable("ImageDecoderHelper")
+    , mImage(Move(aImage))
+    , mInputStream(Move(aInputStream))
+    , mEventTarget(aEventTarget)
+    , mCallback(aCallback)
+    , mCallbackEventTarget(aCallbackEventTarget)
+    , mStatus(NS_OK)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  NS_IMETHOD
+  Run() override
+  {
+    // This runnable is dispatched on the Image thread when reading data, but
+    // at the end, it goes back to the main-thread in order to complete the
+    // operation.
+    if (NS_IsMainThread()) {
+      // Let the Image know we've sent all the data.
+      mImage->OnImageDataComplete(nullptr, nullptr, mStatus, true);
+
+      RefPtr<ProgressTracker> tracker = mImage->GetProgressTracker();
+      tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
+
+      nsCOMPtr<imgIContainer> container;
+      if (NS_SUCCEEDED(mStatus)) {
+        container = do_QueryInterface(mImage);
+      }
+
+      mCallback->OnImageReady(container, mStatus);
+      return NS_OK;
+    }
+
+    uint64_t length;
+    nsresult rv = mInputStream->Available(&length);
+    if (rv == NS_BASE_STREAM_CLOSED) {
+      return OperationCompleted(NS_OK);
+    }
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return OperationCompleted(rv);
+    }
+
+    // Nothing else to read, but maybe we just need to wait.
+    if (length == 0) {
+      nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+        do_QueryInterface(mInputStream);
+      if (asyncInputStream) {
+        rv = asyncInputStream->AsyncWait(this, 0, 0, mEventTarget);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return OperationCompleted(rv);
+        }
+        return NS_OK;
+      }
+
+      // We really have nothing else to read.
+      if (length == 0) {
+        return OperationCompleted(NS_OK);
+      }
+    }
+
+    // Send the source data to the Image.
+    rv = mImage->OnImageDataAvailable(nullptr, nullptr, mInputStream, 0,
+                                      uint32_t(length));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return OperationCompleted(rv);
+    }
+
+    rv = mEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return OperationCompleted(rv);
+    }
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  OnInputStreamReady(nsIAsyncInputStream* aAsyncInputStream) override
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+    return Run();
+  }
+
+  nsresult
+  OperationCompleted(nsresult aStatus)
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    mStatus = aStatus;
+    mCallbackEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
+    return NS_OK;
+  }
+
+private:
+  ~ImageDecoderHelper()
+  {
+    NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mImage",
+                                      mImage.forget());
+    NS_ReleaseOnMainThreadSystemGroup("ImageDecoderHelper::mCallback",
+                                      mCallback.forget());
+  }
+
+  RefPtr<image::Image> mImage;
+
+  nsCOMPtr<nsIInputStream> mInputStream;
+  nsCOMPtr<nsIEventTarget> mEventTarget;
+  nsCOMPtr<imgIContainerCallback> mCallback;
+  nsCOMPtr<nsIEventTarget> mCallbackEventTarget;
+
+  nsresult mStatus;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(ImageDecoderHelper, Runnable,
+                            nsIInputStreamCallback)
+
+} // anonymous
+
 /* ========== imgITools implementation ========== */
 
 
 
 NS_IMPL_ISUPPORTS(imgTools, imgITools)
 
 imgTools::imgTools()
 {
@@ -48,29 +181,44 @@ imgTools::~imgTools()
 
 NS_IMETHODIMP
 imgTools::DecodeImage(nsIInputStream* aInStr,
                       const nsACString& aMimeType,
                       imgIContainer** aContainer)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsresult rv;
+  NS_ENSURE_ARG_POINTER(aInStr);
+
+  // We are not able to decode an async inputStream! Please use
+  // DecodeImageAsync instead.
+  nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aInStr);
+  if (NS_WARN_IF(asyncStream)) {
+    return NS_ERROR_FAILURE;
+  }
 
-  NS_ENSURE_ARG_POINTER(aInStr);
+  bool nonBlocking;
+  nsresult rv = aInStr->IsNonBlocking(&nonBlocking);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // We don't want to block the main-thread. Please use DecodeImageAsync
+  // instead.
+  if (NS_WARN_IF(!nonBlocking)) {
+    MOZ_ASSERT_UNREACHABLE("We don't want to block the main-thread. Please use DecodeImageAsync instead.");
+    return NS_ERROR_FAILURE;
+  }
 
   // Prepare the input stream.
   nsCOMPtr<nsIInputStream> inStream = aInStr;
   if (!NS_InputStreamIsBuffered(aInStr)) {
     nsCOMPtr<nsIInputStream> bufStream;
     rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
                                    inStream.forget(), 1024);
-    if (NS_SUCCEEDED(rv)) {
-      inStream = bufStream;
-    }
+    NS_ENSURE_SUCCESS(rv, rv);
+    inStream = bufStream.forget();
   }
 
   // Figure out how much data we've been passed.
   uint64_t length;
   rv = inStream->Available(&length);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(length <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
@@ -79,28 +227,77 @@ imgTools::DecodeImage(nsIInputStream* aI
   RefPtr<image::Image> image =
     ImageFactory::CreateAnonymousImage(mimeType, uint32_t(length));
   RefPtr<ProgressTracker> tracker = image->GetProgressTracker();
 
   if (image->HasError()) {
     return NS_ERROR_FAILURE;
   }
 
-  // Send the source data to the Image.
+  // If we have anything to read, let's inform the image.
   rv = image->OnImageDataAvailable(nullptr, nullptr, inStream, 0,
                                    uint32_t(length));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Let the Image know we've sent all the data.
   rv = image->OnImageDataComplete(nullptr, nullptr, NS_OK, true);
   tracker->SyncNotifyProgress(FLAG_LOAD_COMPLETE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // All done.
-  NS_ADDREF(*aContainer = image.get());
+  image.forget(aContainer);
+  return NS_OK;
+}
+
+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);
+
+  nsresult rv;
+
+  // Let's continuing the reading on a separate thread.
+  DecodePool* decodePool = DecodePool::Singleton();
+  MOZ_ASSERT(decodePool);
+
+  RefPtr<nsIEventTarget> target = decodePool->GetIOEventTarget();
+  NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+
+  // Prepare the input stream.
+  nsCOMPtr<nsIInputStream> stream = aInStr;
+  if (!NS_InputStreamIsBuffered(aInStr)) {
+    nsCOMPtr<nsIInputStream> bufStream;
+    rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
+                                   stream.forget(), 1024);
+    NS_ENSURE_SUCCESS(rv, rv);
+    stream = bufStream.forget();
+  }
+
+  // Create a new image container to hold the decoded data.
+  nsAutoCString mimeType(aMimeType);
+  RefPtr<image::Image> image = ImageFactory::CreateAnonymousImage(mimeType, 0);
+
+  // Already an error?
+  if (image->HasError()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<ImageDecoderHelper> helper =
+    new ImageDecoderHelper(image.forget(), stream.forget(), target, aCallback,
+                           aEventTarget);
+  rv = target->Dispatch(helper.forget(), NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 /**
  * This takes a DataSourceSurface rather than a SourceSurface because some
  * of the callers have a DataSourceSurface and we don't want to call
  * GetDataSurface on such surfaces since that may incure a conversion to
  * SurfaceType::DATA which we don't need.