image/imgTools.cpp
author Drew Willcoxon <adw@mozilla.com>
Fri, 09 Mar 2018 18:54:17 -0800
changeset 766943 c61f5f5ead48c78a80c80db5c489bdc7cfaf8175
parent 705748 75d2ae4d07c4a88a1fa5d695278c7e106a2e20c1
child 794319 b9ae63e7d9cc1de56c3f3eec36b4bfbf05e56ed9
permissions -rw-r--r--
Back out bug 1426216 (show search suggestions above history in urlbar in some cases) and follow-ups on release. r=Mossop, a=ritu There are four bugs that need to be backed out: 1. https://hg.mozilla.org/releases/mozilla-release/rev/152720a4efe7 (bug 1432716) 2. https://hg.mozilla.org/releases/mozilla-release/rev/84a24eb373fa (bug 1432263) 3. https://hg.mozilla.org/releases/mozilla-release/rev/4d423fdc33dd (bug 1429974) 4. https://hg.mozilla.org/releases/mozilla-release/rev/440dbdb51f94 (bug 1426216) The original bug implementing the problematic feature (pref migration + UI) is 4, and the others are improvements to it.

/* -*- 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 "nsStringStream.h"
#include "nsContentUtils.h"
#include "nsProxyRelease.h"
#include "ImageFactory.h"
#include "Image.h"
#include "ScriptedNotificationObserver.h"
#include "imgIScriptedNotificationObserver.h"
#include "gfxPlatform.h"
#include "jsfriendapi.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()
{
  /* member initializers and constructor code */
}

imgTools::~imgTools()
{
  /* destructor code */
}

NS_IMETHODIMP
imgTools::DecodeImageFromArrayBuffer(JS::HandleValue aArrayBuffer,
                                     const nsACString& aMimeType,
                                     JSContext* aCx,
                                     imgIContainer** aContainer)
{
  if (!aArrayBuffer.isObject()) {
    return NS_ERROR_FAILURE;
  }

  JS::Rooted<JSObject*> obj(aCx,
                            js::UnwrapArrayBuffer(&aArrayBuffer.toObject()));
  if (!obj) {
    return NS_ERROR_FAILURE;
  }

  uint8_t* bufferData = nullptr;
  uint32_t bufferLength = 0;
  bool isSharedMemory = false;

  js::GetArrayBufferLengthAndData(obj, &bufferLength, &isSharedMemory,
                                  &bufferData);
  return DecodeImageFromBuffer((char*)bufferData, bufferLength, aMimeType,
                               aContainer);
}

NS_IMETHODIMP
imgTools::DecodeImageFromBuffer(const char* aBuffer, uint32_t aSize,
                                const nsACString& aMimeType,
                                imgIContainer** aContainer)
{
  MOZ_ASSERT(NS_IsMainThread());

  NS_ENSURE_ARG_POINTER(aBuffer);

  // Create a new image container to hold the decoded data.
  nsAutoCString mimeType(aMimeType);
  RefPtr<image::Image> image =
    ImageFactory::CreateAnonymousImage(mimeType, aSize);
  RefPtr<ProgressTracker> tracker = image->GetProgressTracker();

  if (image->HasError()) {
    return NS_ERROR_FAILURE;
  }

  // Let's create a temporary inputStream.
  nsCOMPtr<nsIInputStream> stream;
  nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream),
                                      aBuffer, aSize,
                                      NS_ASSIGNMENT_DEPEND);
  NS_ENSURE_SUCCESS(rv, rv);
  MOZ_ASSERT(stream);
  MOZ_ASSERT(NS_InputStreamIsBuffered(stream));

  rv = image->OnImageDataAvailable(nullptr, nullptr, stream, 0,
                                   aSize);
  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.
  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.
 */
static nsresult
EncodeImageData(DataSourceSurface* aDataSurface,
                const nsACString& aMimeType,
                const nsAString& aOutputOptions,
                nsIInputStream** aStream)
{
  MOZ_ASSERT(aDataSurface->GetFormat() ==  SurfaceFormat::B8G8R8A8,
             "We're assuming B8G8R8A8");

  // Get an image encoder for the media type
  nsAutoCString encoderCID(
    NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);

  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
  if (!encoder) {
    return NS_IMAGELIB_ERROR_NO_ENCODER;
  }

  DataSourceSurface::MappedSurface map;
  if (!aDataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
    return NS_ERROR_FAILURE;
  }

  IntSize size = aDataSurface->GetSize();
  uint32_t dataLength = map.mStride * size.height;

  // Encode the bitmap
  nsresult rv = encoder->InitFromData(map.mData,
                                      dataLength,
                                      size.width,
                                      size.height,
                                      map.mStride,
                                      imgIEncoder::INPUT_FORMAT_HOSTARGB,
                                      aOutputOptions);
  aDataSurface->Unmap();
  NS_ENSURE_SUCCESS(rv, rv);

  encoder.forget(aStream);
  return NS_OK;
}

NS_IMETHODIMP
imgTools::EncodeImage(imgIContainer* aContainer,
                      const nsACString& aMimeType,
                      const nsAString& aOutputOptions,
                      nsIInputStream** aStream)
{
  // Use frame 0 from the image container.
  RefPtr<SourceSurface> frame =
    aContainer->GetFrame(imgIContainer::FRAME_FIRST,
                         imgIContainer::FLAG_SYNC_DECODE);
  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);

  RefPtr<DataSourceSurface> dataSurface;

  if (frame->GetFormat() == SurfaceFormat::B8G8R8A8) {
    dataSurface = frame->GetDataSurface();
  } else {
    // Convert format to SurfaceFormat::B8G8R8A8
    dataSurface = gfxUtils::
      CopySurfaceToDataSourceSurfaceWithFormat(frame,
                                               SurfaceFormat::B8G8R8A8);
  }

  NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);

  return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
}

NS_IMETHODIMP
imgTools::EncodeScaledImage(imgIContainer* aContainer,
                            const nsACString& aMimeType,
                            int32_t aScaledWidth,
                            int32_t aScaledHeight,
                            const nsAString& aOutputOptions,
                            nsIInputStream** aStream)
{
  NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0);

  // If no scaled size is specified, we'll just encode the image at its
  // original size (no scaling).
  if (aScaledWidth == 0 && aScaledHeight == 0) {
    return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
  }

  // Retrieve the image's size.
  int32_t imageWidth = 0;
  int32_t imageHeight = 0;
  aContainer->GetWidth(&imageWidth);
  aContainer->GetHeight(&imageHeight);

  // If the given width or height is zero we'll replace it with the image's
  // original dimensions.
  IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth,
                     aScaledHeight == 0 ? imageHeight : aScaledHeight);

  // Use frame 0 from the image container.
  RefPtr<SourceSurface> frame =
    aContainer->GetFrameAtSize(scaledSize,
                               imgIContainer::FRAME_FIRST,
                               imgIContainer::FLAG_HIGH_QUALITY_SCALING |
                               imgIContainer::FLAG_SYNC_DECODE);
  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);

  RefPtr<DataSourceSurface> dataSurface =
    Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
  if (NS_WARN_IF(!dataSurface)) {
    return NS_ERROR_FAILURE;
  }

  DataSourceSurface::MappedSurface map;
  if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
    return NS_ERROR_FAILURE;
  }

  RefPtr<DrawTarget> dt =
    Factory::CreateDrawTargetForData(BackendType::CAIRO,
                                     map.mData,
                                     dataSurface->GetSize(),
                                     map.mStride,
                                     SurfaceFormat::B8G8R8A8);
  if (!dt) {
    gfxWarning() << "imgTools::EncodeImage failed in CreateDrawTargetForData";
    return NS_ERROR_OUT_OF_MEMORY;
  }

  IntSize frameSize = frame->GetSize();
  dt->DrawSurface(frame,
                  Rect(0, 0, scaledSize.width, scaledSize.height),
                  Rect(0, 0, frameSize.width, frameSize.height),
                  DrawSurfaceOptions(),
                  DrawOptions(1.0f, CompositionOp::OP_SOURCE));

  dataSurface->Unmap();

  return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
}

NS_IMETHODIMP
imgTools::EncodeCroppedImage(imgIContainer* aContainer,
                             const nsACString& aMimeType,
                             int32_t aOffsetX,
                             int32_t aOffsetY,
                             int32_t aWidth,
                             int32_t aHeight,
                             const nsAString& aOutputOptions,
                             nsIInputStream** aStream)
{
  NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0);

  // Offsets must be zero when no width and height are given or else we're out
  // of bounds.
  NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0);

  // If no size is specified then we'll preserve the image's original dimensions
  // and don't need to crop.
  if (aWidth == 0 && aHeight == 0) {
    return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
  }

  // Use frame 0 from the image container.
  RefPtr<SourceSurface> frame =
    aContainer->GetFrame(imgIContainer::FRAME_FIRST,
                         imgIContainer::FLAG_SYNC_DECODE);
  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);

  int32_t frameWidth = frame->GetSize().width;
  int32_t frameHeight = frame->GetSize().height;

  // If the given width or height is zero we'll replace it with the image's
  // original dimensions.
  if (aWidth == 0) {
    aWidth = frameWidth;
  } else if (aHeight == 0) {
    aHeight = frameHeight;
  }

  // Check that the given crop rectangle is within image bounds.
  NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth &&
                frameHeight >= aOffsetY + aHeight);

  RefPtr<DataSourceSurface> dataSurface =
    Factory::CreateDataSourceSurface(IntSize(aWidth, aHeight),
                                     SurfaceFormat::B8G8R8A8,
                                     /* aZero = */ true);
  if (NS_WARN_IF(!dataSurface)) {
    return NS_ERROR_FAILURE;
  }

  DataSourceSurface::MappedSurface map;
  if (!dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)) {
    return NS_ERROR_FAILURE;
  }

  RefPtr<DrawTarget> dt =
    Factory::CreateDrawTargetForData(BackendType::CAIRO,
                                     map.mData,
                                     dataSurface->GetSize(),
                                     map.mStride,
                                     SurfaceFormat::B8G8R8A8);
  if (!dt) {
    gfxWarning() <<
      "imgTools::EncodeCroppedImage failed in CreateDrawTargetForData";
    return NS_ERROR_OUT_OF_MEMORY;
  }
  dt->CopySurface(frame,
                  IntRect(aOffsetX, aOffsetY, aWidth, aHeight),
                  IntPoint(0, 0));

  dataSurface->Unmap();

  return EncodeImageData(dataSurface, aMimeType, aOutputOptions, aStream);
}

NS_IMETHODIMP
imgTools::CreateScriptedObserver(imgIScriptedNotificationObserver* aInner,
                                 imgINotificationObserver** aObserver)
{
  NS_ADDREF(*aObserver = new ScriptedNotificationObserver(aInner));
  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);
}

} // namespace image
} // namespace mozilla