summaryrefslogtreecommitdiffstats
path: root/dom/canvas/ImageBitmap.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/ImageBitmap.cpp')
-rw-r--r--dom/canvas/ImageBitmap.cpp2133
1 files changed, 2133 insertions, 0 deletions
diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp
new file mode 100644
index 000000000..6588e0aa3
--- /dev/null
+++ b/dom/canvas/ImageBitmap.cpp
@@ -0,0 +1,2133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/dom/ImageBitmap.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/gfx/2D.h"
+#include "ImageBitmapColorUtils.h"
+#include "ImageBitmapUtils.h"
+#include "ImageUtils.h"
+#include "imgTools.h"
+#include "libyuv.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmap, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/*
+ * If either aRect.width or aRect.height are negative, then return a new IntRect
+ * which represents the same rectangle as the aRect does but with positive width
+ * and height.
+ */
+static IntRect
+FixUpNegativeDimension(const IntRect& aRect, ErrorResult& aRv)
+{
+ gfx::IntRect rect = aRect;
+
+ // fix up negative dimensions
+ if (rect.width < 0) {
+ CheckedInt32 checkedX = CheckedInt32(rect.x) + rect.width;
+
+ if (!checkedX.isValid()) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return rect;
+ }
+
+ rect.x = checkedX.value();
+ rect.width = -(rect.width);
+ }
+
+ if (rect.height < 0) {
+ CheckedInt32 checkedY = CheckedInt32(rect.y) + rect.height;
+
+ if (!checkedY.isValid()) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return rect;
+ }
+
+ rect.y = checkedY.value();
+ rect.height = -(rect.height);
+ }
+
+ return rect;
+}
+
+/*
+ * This helper function copies the data of the given DataSourceSurface,
+ * _aSurface_, in the given area, _aCropRect_, into a new DataSourceSurface.
+ * This might return null if it can not create a new SourceSurface or it cannot
+ * read data from the given _aSurface_.
+ *
+ * Warning: Even though the area of _aCropRect_ is just the same as the size of
+ * _aSurface_, this function still copy data into a new
+ * DataSourceSurface.
+ */
+static already_AddRefed<DataSourceSurface>
+CropAndCopyDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect)
+{
+ MOZ_ASSERT(aSurface);
+
+ // Check the aCropRect
+ ErrorResult error;
+ const IntRect positiveCropRect = FixUpNegativeDimension(aCropRect, error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ return nullptr;
+ }
+
+ // Calculate the size of the new SourceSurface.
+ // We cannot keep using aSurface->GetFormat() to create new DataSourceSurface,
+ // since it might be SurfaceFormat::B8G8R8X8 which does not handle opacity,
+ // however the specification explicitly define that "If any of the pixels on
+ // this rectangle are outside the area where the input bitmap was placed, then
+ // they will be transparent black in output."
+ // So, instead, we force the output format to be SurfaceFormat::B8G8R8A8.
+ const SurfaceFormat format = SurfaceFormat::B8G8R8A8;
+ const int bytesPerPixel = BytesPerPixel(format);
+ const IntSize dstSize = IntSize(positiveCropRect.width,
+ positiveCropRect.height);
+ const uint32_t dstStride = dstSize.width * bytesPerPixel;
+
+ // Create a new SourceSurface.
+ RefPtr<DataSourceSurface> dstDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride, true);
+
+ if (NS_WARN_IF(!dstDataSurface)) {
+ return nullptr;
+ }
+
+ // Only do copying and cropping when the positiveCropRect intersects with
+ // the size of aSurface.
+ const IntRect surfRect(IntPoint(0, 0), aSurface->GetSize());
+ if (surfRect.Intersects(positiveCropRect)) {
+ const IntRect surfPortion = surfRect.Intersect(positiveCropRect);
+ const IntPoint dest(std::max(0, surfPortion.X() - positiveCropRect.X()),
+ std::max(0, surfPortion.Y() - positiveCropRect.Y()));
+
+ // Copy the raw data into the newly created DataSourceSurface.
+ DataSourceSurface::ScopedMap srcMap(aSurface, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE);
+ if (NS_WARN_IF(!srcMap.IsMapped()) ||
+ NS_WARN_IF(!dstMap.IsMapped())) {
+ return nullptr;
+ }
+
+ uint8_t* srcBufferPtr = srcMap.GetData() + surfPortion.y * srcMap.GetStride()
+ + surfPortion.x * bytesPerPixel;
+ uint8_t* dstBufferPtr = dstMap.GetData() + dest.y * dstMap.GetStride()
+ + dest.x * bytesPerPixel;
+ CheckedInt<uint32_t> copiedBytesPerRaw =
+ CheckedInt<uint32_t>(surfPortion.width) * bytesPerPixel;
+ if (!copiedBytesPerRaw.isValid()) {
+ return nullptr;
+ }
+
+ for (int i = 0; i < surfPortion.height; ++i) {
+ memcpy(dstBufferPtr, srcBufferPtr, copiedBytesPerRaw.value());
+ srcBufferPtr += srcMap.GetStride();
+ dstBufferPtr += dstMap.GetStride();
+ }
+ }
+
+ return dstDataSurface.forget();
+}
+
+/*
+ * Encapsulate the given _aSurface_ into a layers::SourceSurfaceImage.
+ */
+static already_AddRefed<layers::Image>
+CreateImageFromSurface(SourceSurface* aSurface)
+{
+ MOZ_ASSERT(aSurface);
+ RefPtr<layers::SourceSurfaceImage> image =
+ new layers::SourceSurfaceImage(aSurface->GetSize(), aSurface);
+ return image.forget();
+}
+
+/*
+ * CreateImageFromRawData(), CreateSurfaceFromRawData() and
+ * CreateImageFromRawDataInMainThreadSyncTask are helpers for
+ * create-from-ImageData case
+ */
+static already_AddRefed<SourceSurface>
+CreateSurfaceFromRawData(const gfx::IntSize& aSize,
+ uint32_t aStride,
+ gfx::SurfaceFormat aFormat,
+ uint8_t* aBuffer,
+ uint32_t aBufferLength,
+ const Maybe<IntRect>& aCropRect)
+{
+ MOZ_ASSERT(!aSize.IsEmpty());
+ MOZ_ASSERT(aBuffer);
+
+ // Wrap the source buffer into a SourceSurface.
+ RefPtr<DataSourceSurface> dataSurface =
+ Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize, aFormat);
+
+ if (NS_WARN_IF(!dataSurface)) {
+ return nullptr;
+ }
+
+ // The temporary cropRect variable is equal to the size of source buffer if we
+ // do not need to crop, or it equals to the given cropping size.
+ const IntRect cropRect = aCropRect.valueOr(IntRect(0, 0, aSize.width, aSize.height));
+
+ // Copy the source buffer in the _cropRect_ area into a new SourceSurface.
+ RefPtr<DataSourceSurface> result = CropAndCopyDataSourceSurface(dataSurface, cropRect);
+
+ if (NS_WARN_IF(!result)) {
+ return nullptr;
+ }
+
+ return result.forget();
+}
+
+static already_AddRefed<layers::Image>
+CreateImageFromRawData(const gfx::IntSize& aSize,
+ uint32_t aStride,
+ gfx::SurfaceFormat aFormat,
+ uint8_t* aBuffer,
+ uint32_t aBufferLength,
+ const Maybe<IntRect>& aCropRect)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Copy and crop the source buffer into a SourceSurface.
+ RefPtr<SourceSurface> rgbaSurface =
+ CreateSurfaceFromRawData(aSize, aStride, aFormat,
+ aBuffer, aBufferLength,
+ aCropRect);
+
+ if (NS_WARN_IF(!rgbaSurface)) {
+ return nullptr;
+ }
+
+ // Convert RGBA to BGRA
+ RefPtr<DataSourceSurface> rgbaDataSurface = rgbaSurface->GetDataSurface();
+ RefPtr<DataSourceSurface> bgraDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(),
+ SurfaceFormat::B8G8R8A8,
+ rgbaDataSurface->Stride());
+
+ DataSourceSurface::MappedSurface rgbaMap;
+ DataSourceSurface::MappedSurface bgraMap;
+
+ if (NS_WARN_IF(!rgbaDataSurface->Map(DataSourceSurface::MapType::READ, &rgbaMap)) ||
+ NS_WARN_IF(!bgraDataSurface->Map(DataSourceSurface::MapType::WRITE, &bgraMap))) {
+ return nullptr;
+ }
+
+ libyuv::ABGRToARGB(rgbaMap.mData, rgbaMap.mStride,
+ bgraMap.mData, bgraMap.mStride,
+ bgraDataSurface->GetSize().width,
+ bgraDataSurface->GetSize().height);
+
+ rgbaDataSurface->Unmap();
+ bgraDataSurface->Unmap();
+
+ // Create an Image from the BGRA SourceSurface.
+ RefPtr<layers::Image> image = CreateImageFromSurface(bgraDataSurface);
+
+ if (NS_WARN_IF(!image)) {
+ return nullptr;
+ }
+
+ return image.forget();
+}
+
+/*
+ * This is a synchronous task.
+ * This class is used to create a layers::SourceSurfaceImage from raw data in the main
+ * thread. While creating an ImageBitmap from an ImageData, we need to create
+ * a SouceSurface from the ImageData's raw data and then set the SourceSurface
+ * into a layers::SourceSurfaceImage. However, the layers::SourceSurfaceImage asserts the
+ * setting operation in the main thread, so if we are going to create an
+ * ImageBitmap from an ImageData off the main thread, we post an event to the
+ * main thread to create a layers::SourceSurfaceImage from an ImageData's raw data.
+ */
+class CreateImageFromRawDataInMainThreadSyncTask final :
+ public WorkerMainThreadRunnable
+{
+public:
+ CreateImageFromRawDataInMainThreadSyncTask(uint8_t* aBuffer,
+ uint32_t aBufferLength,
+ uint32_t aStride,
+ gfx::SurfaceFormat aFormat,
+ const gfx::IntSize& aSize,
+ const Maybe<IntRect>& aCropRect,
+ layers::Image** aImage)
+ : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
+ NS_LITERAL_CSTRING("ImageBitmap :: Create Image from Raw Data"))
+ , mImage(aImage)
+ , mBuffer(aBuffer)
+ , mBufferLength(aBufferLength)
+ , mStride(aStride)
+ , mFormat(aFormat)
+ , mSize(aSize)
+ , mCropRect(aCropRect)
+ {
+ MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromRawDataInMainThreadSyncTask.");
+ }
+
+ bool MainThreadRun() override
+ {
+ RefPtr<layers::Image> image =
+ CreateImageFromRawData(mSize, mStride, mFormat,
+ mBuffer, mBufferLength,
+ mCropRect);
+
+ if (NS_WARN_IF(!image)) {
+ return false;
+ }
+
+ image.forget(mImage);
+
+ return true;
+ }
+
+private:
+ layers::Image** mImage;
+ uint8_t* mBuffer;
+ uint32_t mBufferLength;
+ uint32_t mStride;
+ gfx::SurfaceFormat mFormat;
+ gfx::IntSize mSize;
+ const Maybe<IntRect>& mCropRect;
+};
+
+static bool
+CheckSecurityForHTMLElements(bool aIsWriteOnly, bool aCORSUsed, nsIPrincipal* aPrincipal)
+{
+ MOZ_ASSERT(aPrincipal);
+
+ if (aIsWriteOnly) {
+ return false;
+ }
+
+ if (!aCORSUsed) {
+ nsIGlobalObject* incumbentSettingsObject = GetIncumbentGlobal();
+ if (NS_WARN_IF(!incumbentSettingsObject)) {
+ return false;
+ }
+
+ nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull();
+ if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+CheckSecurityForHTMLElements(const nsLayoutUtils::SurfaceFromElementResult& aRes)
+{
+ return CheckSecurityForHTMLElements(aRes.mIsWriteOnly, aRes.mCORSUsed, aRes.mPrincipal);
+}
+
+/*
+ * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the
+ * security checking.
+ */
+template<class HTMLElementType>
+static already_AddRefed<SourceSurface>
+GetSurfaceFromElement(nsIGlobalObject* aGlobal, HTMLElementType& aElement, ErrorResult& aRv)
+{
+ nsLayoutUtils::SurfaceFromElementResult res =
+ nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME);
+
+ // check origin-clean
+ if (!CheckSecurityForHTMLElements(res)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> surface = res.GetSourceSurface();
+
+ if (NS_WARN_IF(!surface)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ return surface.forget();
+}
+
+/*
+ * The specification doesn't allow to create an ImegeBitmap from a vector image.
+ * This function is used to check if the given HTMLImageElement contains a
+ * raster image.
+ */
+static bool
+HasRasterImage(HTMLImageElement& aImageEl)
+{
+ nsresult rv;
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ rv = aImageEl.GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+ if (NS_SUCCEEDED(rv) && imgRequest) {
+ nsCOMPtr<imgIContainer> imgContainer;
+ rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
+ if (NS_SUCCEEDED(rv) && imgContainer &&
+ imgContainer->GetType() == imgIContainer::TYPE_RASTER) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
+ bool aIsPremultipliedAlpha /* = true */)
+ : mParent(aGlobal)
+ , mData(aData)
+ , mSurface(nullptr)
+ , mDataWrapper(new ImageUtils(mData))
+ , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
+ , mIsPremultipliedAlpha(aIsPremultipliedAlpha)
+ , mIsCroppingAreaOutSideOfSourceImage(false)
+{
+ MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
+}
+
+ImageBitmap::~ImageBitmap()
+{
+}
+
+JSObject*
+ImageBitmap::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return ImageBitmapBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+ImageBitmap::Close()
+{
+ mData = nullptr;
+ mSurface = nullptr;
+ mPictureRect.SetEmpty();
+}
+
+void
+ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv)
+{
+ mPictureRect = FixUpNegativeDimension(aRect, aRv);
+}
+
+void
+ImageBitmap::SetIsCroppingAreaOutSideOfSourceImage(const IntSize& aSourceSize,
+ const Maybe<IntRect>& aCroppingRect)
+{
+ // No cropping at all.
+ if (aCroppingRect.isNothing()) {
+ mIsCroppingAreaOutSideOfSourceImage = false;
+ return;
+ }
+
+ if (aCroppingRect->X() < 0 || aCroppingRect->Y() < 0 ||
+ aCroppingRect->Width() > aSourceSize.width ||
+ aCroppingRect->Height() > aSourceSize.height) {
+ mIsCroppingAreaOutSideOfSourceImage = true;
+ }
+}
+
+static already_AddRefed<SourceSurface>
+ConvertColorFormatIfNeeded(RefPtr<SourceSurface> aSurface)
+{
+ const SurfaceFormat srcFormat = aSurface->GetFormat();
+ if (srcFormat == SurfaceFormat::R8G8B8A8 ||
+ srcFormat == SurfaceFormat::B8G8R8A8 ||
+ srcFormat == SurfaceFormat::R8G8B8X8 ||
+ srcFormat == SurfaceFormat::B8G8R8X8 ||
+ srcFormat == SurfaceFormat::A8R8G8B8 ||
+ srcFormat == SurfaceFormat::X8R8G8B8) {
+ return aSurface.forget();
+ }
+
+ if (srcFormat == SurfaceFormat::A8 ||
+ srcFormat == SurfaceFormat::Depth) {
+ return nullptr;
+ }
+
+ const int bytesPerPixel = BytesPerPixel(SurfaceFormat::B8G8R8A8);
+ const IntSize dstSize = aSurface->GetSize();
+ const uint32_t dstStride = dstSize.width * bytesPerPixel;
+
+ RefPtr<DataSourceSurface> dstDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(dstSize,
+ SurfaceFormat::B8G8R8A8,
+ dstStride);
+
+ RefPtr<DataSourceSurface> srcDataSurface = aSurface->GetDataSurface();
+ if (NS_WARN_IF(!srcDataSurface)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap srcMap(srcDataSurface, DataSourceSurface::READ);
+ DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE);
+ if (NS_WARN_IF(!srcMap.IsMapped()) || NS_WARN_IF(!dstMap.IsMapped())) {
+ return nullptr;
+ }
+
+ int rv = 0;
+ if (srcFormat == SurfaceFormat::R8G8B8) {
+ rv = RGB24ToBGRA32(srcMap.GetData(), srcMap.GetStride(),
+ dstMap.GetData(), dstMap.GetStride(),
+ dstSize.width, dstSize.height);
+ } else if (srcFormat == SurfaceFormat::B8G8R8) {
+ rv = BGR24ToBGRA32(srcMap.GetData(), srcMap.GetStride(),
+ dstMap.GetData(), dstMap.GetStride(),
+ dstSize.width, dstSize.height);
+ } else if (srcFormat == SurfaceFormat::HSV) {
+ rv = HSVToBGRA32((const float*)srcMap.GetData(), srcMap.GetStride(),
+ dstMap.GetData(), dstMap.GetStride(),
+ dstSize.width, dstSize.height);
+ } else if (srcFormat == SurfaceFormat::Lab) {
+ rv = LabToBGRA32((const float*)srcMap.GetData(), srcMap.GetStride(),
+ dstMap.GetData(), dstMap.GetStride(),
+ dstSize.width, dstSize.height);
+ }
+
+ if (NS_WARN_IF(rv != 0)) {
+ return nullptr;
+ }
+
+ return dstDataSurface.forget();
+}
+
+/*
+ * The functionality of PrepareForDrawTarget method:
+ * (1) Get a SourceSurface from the mData (which is a layers::Image).
+ * (2) Convert the SourceSurface to format B8G8R8A8 if the original format is
+ * R8G8B8, B8G8R8, HSV or Lab.
+ * Note: if the original format is A8 or Depth, then return null directly.
+ * (3) Do cropping if the size of SourceSurface does not equal to the
+ * mPictureRect.
+ * (4) Pre-multiply alpha if needed.
+ */
+already_AddRefed<SourceSurface>
+ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget)
+{
+ MOZ_ASSERT(aTarget);
+
+ if (!mData) {
+ return nullptr;
+ }
+
+ if (!mSurface) {
+ mSurface = mData->GetAsSourceSurface();
+
+ if (!mSurface) {
+ return nullptr;
+ }
+ }
+
+ // Check if we need to convert the format.
+ // Convert R8G8B8/B8G8R8/HSV/Lab to B8G8R8A8.
+ // Return null if the original format is A8 or Depth.
+ mSurface = ConvertColorFormatIfNeeded(mSurface);
+ if (NS_WARN_IF(!mSurface)) {
+ return nullptr;
+ }
+
+ RefPtr<DrawTarget> target = aTarget;
+ IntRect surfRect(0, 0, mSurface->GetSize().width, mSurface->GetSize().height);
+
+ // Check if we still need to crop our surface
+ if (!mPictureRect.IsEqualEdges(surfRect)) {
+
+ IntRect surfPortion = surfRect.Intersect(mPictureRect);
+
+ // the crop lies entirely outside the surface area, nothing to draw
+ if (surfPortion.IsEmpty()) {
+ mSurface = nullptr;
+ RefPtr<gfx::SourceSurface> surface(mSurface);
+ return surface.forget();
+ }
+
+ IntPoint dest(std::max(0, surfPortion.X() - mPictureRect.X()),
+ std::max(0, surfPortion.Y() - mPictureRect.Y()));
+
+ // We must initialize this target with mPictureRect.Size() because the
+ // specification states that if the cropping area is given, then return an
+ // ImageBitmap with the size equals to the cropping area.
+ target = target->CreateSimilarDrawTarget(mPictureRect.Size(),
+ target->GetFormat());
+
+ if (!target) {
+ mSurface = nullptr;
+ RefPtr<gfx::SourceSurface> surface(mSurface);
+ return surface.forget();
+ }
+
+ target->CopySurface(mSurface, surfPortion, dest);
+ mSurface = target->Snapshot();
+
+ // Make mCropRect match new surface we've cropped to
+ mPictureRect.MoveTo(0, 0);
+ }
+
+ // Pre-multiply alpha here.
+ // Apply pre-multiply alpha only if mIsPremultipliedAlpha is false.
+ // Ignore this step if the source surface does not have alpha channel; this
+ // kind of source surfaces might come form layers::PlanarYCbCrImage.
+ if (!mIsPremultipliedAlpha &&
+ mSurface->GetFormat() != SurfaceFormat::B8G8R8X8 &&
+ mSurface->GetFormat() != SurfaceFormat::R8G8B8X8 &&
+ mSurface->GetFormat() != SurfaceFormat::X8R8G8B8) {
+ MOZ_ASSERT(mSurface->GetFormat() == SurfaceFormat::R8G8B8A8 ||
+ mSurface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ mSurface->GetFormat() == SurfaceFormat::A8R8G8B8);
+
+ RefPtr<DataSourceSurface> dstSurface = mSurface->GetDataSurface();
+ MOZ_ASSERT(dstSurface);
+
+ RefPtr<DataSourceSurface> srcSurface;
+ DataSourceSurface::MappedSurface srcMap;
+ DataSourceSurface::MappedSurface dstMap;
+
+ if (!dstSurface->Map(DataSourceSurface::MapType::READ_WRITE, &dstMap)) {
+ srcSurface = dstSurface;
+ if (!srcSurface->Map(DataSourceSurface::READ, &srcMap)) {
+ gfxCriticalError() << "Failed to map source surface for premultiplying alpha.";
+ return nullptr;
+ }
+
+ dstSurface = Factory::CreateDataSourceSurface(srcSurface->GetSize(), srcSurface->GetFormat());
+
+ if (!dstSurface || !dstSurface->Map(DataSourceSurface::MapType::WRITE, &dstMap)) {
+ gfxCriticalError() << "Failed to map destination surface for premultiplying alpha.";
+ srcSurface->Unmap();
+ return nullptr;
+ }
+ }
+
+ uint8_t rIndex = 0;
+ uint8_t gIndex = 0;
+ uint8_t bIndex = 0;
+ uint8_t aIndex = 0;
+
+ if (mSurface->GetFormat() == SurfaceFormat::R8G8B8A8) {
+ rIndex = 0;
+ gIndex = 1;
+ bIndex = 2;
+ aIndex = 3;
+ } else if (mSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ rIndex = 2;
+ gIndex = 1;
+ bIndex = 0;
+ aIndex = 3;
+ } else if (mSurface->GetFormat() == SurfaceFormat::A8R8G8B8) {
+ rIndex = 1;
+ gIndex = 2;
+ bIndex = 3;
+ aIndex = 0;
+ }
+
+ for (int i = 0; i < dstSurface->GetSize().height; ++i) {
+ uint8_t* bufferPtr = dstMap.mData + dstMap.mStride * i;
+ uint8_t* srcBufferPtr = srcSurface ? srcMap.mData + srcMap.mStride * i : bufferPtr;
+ for (int i = 0; i < dstSurface->GetSize().width; ++i) {
+ uint8_t r = *(srcBufferPtr+rIndex);
+ uint8_t g = *(srcBufferPtr+gIndex);
+ uint8_t b = *(srcBufferPtr+bIndex);
+ uint8_t a = *(srcBufferPtr+aIndex);
+
+ *(bufferPtr+rIndex) = gfxUtils::sPremultiplyTable[a * 256 + r];
+ *(bufferPtr+gIndex) = gfxUtils::sPremultiplyTable[a * 256 + g];
+ *(bufferPtr+bIndex) = gfxUtils::sPremultiplyTable[a * 256 + b];
+ *(bufferPtr+aIndex) = a;
+
+ bufferPtr += 4;
+ srcBufferPtr += 4;
+ }
+ }
+
+ dstSurface->Unmap();
+ if (srcSurface) {
+ srcSurface->Unmap();
+ }
+
+ mSurface = dstSurface;
+ }
+
+ // Replace our surface with one optimized for the target we're about to draw
+ // to, under the assumption it'll likely be drawn again to that target.
+ // This call should be a no-op for already-optimized surfaces
+ mSurface = target->OptimizeSourceSurface(mSurface);
+
+ RefPtr<gfx::SourceSurface> surface(mSurface);
+ return surface.forget();
+}
+
+already_AddRefed<layers::Image>
+ImageBitmap::TransferAsImage()
+{
+ RefPtr<layers::Image> image = mData;
+ Close();
+ return image.forget();
+}
+
+UniquePtr<ImageBitmapCloneData>
+ImageBitmap::ToCloneData() const
+{
+ UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
+ result->mPictureRect = mPictureRect;
+ result->mIsPremultipliedAlpha = mIsPremultipliedAlpha;
+ result->mIsCroppingAreaOutSideOfSourceImage = mIsCroppingAreaOutSideOfSourceImage;
+ RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
+ result->mSurface = surface->GetDataSurface();
+ MOZ_ASSERT(result->mSurface);
+
+ return Move(result);
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
+ ImageBitmapCloneData* aData)
+{
+ RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface);
+
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data,
+ aData->mIsPremultipliedAlpha);
+
+ ret->mIsCroppingAreaOutSideOfSourceImage =
+ aData->mIsCroppingAreaOutSideOfSourceImage;
+
+ ErrorResult rv;
+ ret->SetPictureRect(aData->mPictureRect, rv);
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
+ OffscreenCanvas& aOffscreenCanvas,
+ ErrorResult& aRv)
+{
+ // Check origin-clean.
+ if (aOffscreenCanvas.IsWriteOnly()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ nsLayoutUtils::SurfaceFromElementResult res =
+ nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas,
+ nsLayoutUtils::SFE_WANT_FIRST_FRAME);
+
+ RefPtr<SourceSurface> surface = res.GetSourceSurface();
+
+ if (NS_WARN_IF(!surface)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<layers::Image> data =
+ CreateImageFromSurface(surface);
+
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+ // Check if the image element is completely available or not.
+ if (!aImageEl.Complete()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Check if the image element is a bitmap (e.g. it's a vector graphic) or not.
+ if (!HasRasterImage(aImageEl)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Get the SourceSurface out from the image element and then do security
+ // checking.
+ RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Create ImageBitmap.
+ RefPtr<layers::Image> data = CreateImageFromSurface(surface);
+
+ if (NS_WARN_IF(!data)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
+
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+ aVideoEl.MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_IMAGEBITMAP);
+
+ // Check network state.
+ if (aVideoEl.NetworkState() == HTMLMediaElement::NETWORK_EMPTY) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Check ready state.
+ // Cannot be HTMLMediaElement::HAVE_NOTHING or HTMLMediaElement::HAVE_METADATA.
+ if (aVideoEl.ReadyState() <= HTMLMediaElement::HAVE_METADATA) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Check security.
+ nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal();
+ bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
+ if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ // Create ImageBitmap.
+ ImageContainer *container = aVideoEl.GetImageContainer();
+
+ if (!container) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ AutoLockImage lockImage(container);
+ layers::Image* data = lockImage.GetImage();
+ if (!data) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(data->GetSize(), aCropRect);
+
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+ if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> surface = GetSurfaceFromElement(aGlobal, aCanvasEl, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Crop the source surface if needed.
+ RefPtr<SourceSurface> croppedSurface;
+ IntRect cropRect = aCropRect.valueOr(IntRect());
+
+ // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot
+ // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy
+ // of the rendering context. We handle cropping in this case.
+ if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 ||
+ aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) &&
+ aCropRect.isSome()) {
+ // The _surface_ must be a DataSourceSurface.
+ MOZ_ASSERT(surface->GetType() == SurfaceType::DATA,
+ "The snapshot SourceSurface from WebGL rendering contest is not \
+ DataSourceSurface.");
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ croppedSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
+ cropRect.MoveTo(0, 0);
+ }
+ else {
+ croppedSurface = surface;
+ }
+
+ if (NS_WARN_IF(!croppedSurface)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ // Create an Image from the SourceSurface.
+ RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface);
+
+ if (NS_WARN_IF(!data)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(cropRect, aRv);
+ }
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
+
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+ // Copy data into SourceSurface.
+ dom::Uint8ClampedArray array;
+ DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
+ MOZ_ASSERT(inited);
+
+ array.ComputeLengthAndData();
+ const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
+ const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT);
+ const uint32_t imageWidth = aImageData.Width();
+ const uint32_t imageHeight = aImageData.Height();
+ const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL;
+ const uint32_t dataLength = array.Length();
+ const gfx::IntSize imageSize(imageWidth, imageHeight);
+
+ // Check the ImageData is neutered or not.
+ if (imageWidth == 0 || imageHeight == 0 ||
+ (imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Create and Crop the raw data into a layers::Image
+ RefPtr<layers::Image> data;
+ if (NS_IsMainThread()) {
+ data = CreateImageFromRawData(imageSize, imageStride, FORMAT,
+ array.Data(), dataLength,
+ aCropRect);
+ } else {
+ RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task
+ = new CreateImageFromRawDataInMainThreadSyncTask(array.Data(),
+ dataLength,
+ imageStride,
+ FORMAT,
+ imageSize,
+ aCropRect,
+ getter_AddRefs(data));
+ task->Dispatch(aRv);
+ }
+
+ if (NS_WARN_IF(!data)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ // Create an ImageBimtap.
+ // ImageData's underlying data is not alpha-premultiplied.
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, false);
+
+ // The cropping information has been handled in the CreateImageFromRawData()
+ // function.
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(imageSize, aCropRect);
+
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+ // Check origin-clean.
+ if (aCanvasCtx.GetCanvas()->IsWriteOnly()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ RefPtr<SourceSurface> surface = aCanvasCtx.GetSurfaceSnapshot();
+
+ if (NS_WARN_IF(!surface)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ const IntSize surfaceSize = surface->GetSize();
+ if (surfaceSize.width == 0 || surfaceSize.height == 0) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<layers::Image> data = CreateImageFromSurface(surface);
+
+ if (NS_WARN_IF(!data)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
+
+ return ret.forget();
+}
+
+/* static */ already_AddRefed<ImageBitmap>
+ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
+{
+ if (!aImageBitmap.mData) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+
+ RefPtr<layers::Image> data = aImageBitmap.mData;
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mIsPremultipliedAlpha);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ if (aImageBitmap.mIsCroppingAreaOutSideOfSourceImage == true) {
+ ret->mIsCroppingAreaOutSideOfSourceImage = true;
+ } else {
+ ret->SetIsCroppingAreaOutSideOfSourceImage(aImageBitmap.mPictureRect.Size(),
+ aCropRect);
+ }
+
+ return ret.forget();
+}
+
+class FulfillImageBitmapPromise
+{
+protected:
+ FulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
+ : mPromise(aPromise)
+ , mImageBitmap(aImageBitmap)
+ {
+ MOZ_ASSERT(aPromise);
+ }
+
+ void DoFulfillImageBitmapPromise()
+ {
+ mPromise->MaybeResolve(mImageBitmap);
+ }
+
+private:
+ RefPtr<Promise> mPromise;
+ RefPtr<ImageBitmap> mImageBitmap;
+};
+
+class FulfillImageBitmapPromiseTask final : public Runnable,
+ public FulfillImageBitmapPromise
+{
+public:
+ FulfillImageBitmapPromiseTask(Promise* aPromise, ImageBitmap* aImageBitmap)
+ : FulfillImageBitmapPromise(aPromise, aImageBitmap)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ DoFulfillImageBitmapPromise();
+ return NS_OK;
+ }
+};
+
+class FulfillImageBitmapPromiseWorkerTask final : public WorkerSameThreadRunnable,
+ public FulfillImageBitmapPromise
+{
+public:
+ FulfillImageBitmapPromiseWorkerTask(Promise* aPromise, ImageBitmap* aImageBitmap)
+ : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
+ FulfillImageBitmapPromise(aPromise, aImageBitmap)
+ {
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ DoFulfillImageBitmapPromise();
+ return true;
+ }
+};
+
+static void
+AsyncFulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
+{
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task =
+ new FulfillImageBitmapPromiseTask(aPromise, aImageBitmap);
+ NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
+ } else {
+ RefPtr<FulfillImageBitmapPromiseWorkerTask> task =
+ new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap);
+ task->Dispatch(); // Actually, to the current worker-thread.
+ }
+}
+
+static already_AddRefed<SourceSurface>
+DecodeBlob(Blob& aBlob)
+{
+ // Get the internal stream of the blob.
+ nsCOMPtr<nsIInputStream> stream;
+ ErrorResult error;
+ aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ return nullptr;
+ }
+
+ // Get the MIME type string of the blob.
+ // The type will be checked in the DecodeImage() method.
+ nsAutoString mimeTypeUTF16;
+ aBlob.GetType(mimeTypeUTF16);
+
+ // Get the Component object.
+ nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
+ if (NS_WARN_IF(!imgtool)) {
+ return nullptr;
+ }
+
+ // Decode image.
+ NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeTypeUTF16); // NS_ConvertUTF16toUTF8 ---|> nsAutoCString
+ nsCOMPtr<imgIContainer> imgContainer;
+ nsresult rv = imgtool->DecodeImage(stream, mimeTypeUTF8, getter_AddRefs(imgContainer));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Get the surface out.
+ uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_WANT_DATA_SURFACE;
+ uint32_t whichFrame = imgIContainer::FRAME_FIRST;
+ RefPtr<SourceSurface> surface = imgContainer->GetFrame(whichFrame, frameFlags);
+
+ if (NS_WARN_IF(!surface)) {
+ return nullptr;
+ }
+
+ return surface.forget();
+}
+
+static already_AddRefed<layers::Image>
+DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect,
+ /*Output*/ IntSize& sourceSize)
+{
+ // Decode the blob into a SourceSurface.
+ RefPtr<SourceSurface> surface = DecodeBlob(aBlob);
+
+ if (NS_WARN_IF(!surface)) {
+ return nullptr;
+ }
+
+ // Set the _sourceSize_ output parameter.
+ sourceSize = surface->GetSize();
+
+ // Crop the source surface if needed.
+ RefPtr<SourceSurface> croppedSurface = surface;
+
+ if (aCropRect.isSome()) {
+ // The blob is just decoded into a RasterImage and not optimized yet, so the
+ // _surface_ we get is a DataSourceSurface which wraps the RasterImage's
+ // raw buffer.
+ //
+ // The _surface_ might already be optimized so that its type is not
+ // SurfaceType::DATA. However, we could keep using the generic cropping and
+ // copying since the decoded buffer is only used in this ImageBitmap so we
+ // should crop it to save memory usage.
+ //
+ // TODO: Bug1189632 is going to refactor this create-from-blob part to
+ // decode the blob off the main thread. Re-check if we should do
+ // cropping at this moment again there.
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ croppedSurface = CropAndCopyDataSourceSurface(dataSurface, aCropRect.ref());
+ aCropRect->MoveTo(0, 0);
+ }
+
+ if (NS_WARN_IF(!croppedSurface)) {
+ return nullptr;
+ }
+
+ // Create an Image from the source surface.
+ RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface);
+
+ if (NS_WARN_IF(!image)) {
+ return nullptr;
+ }
+
+ return image.forget();
+}
+
+class CreateImageBitmapFromBlob
+{
+protected:
+ CreateImageBitmapFromBlob(Promise* aPromise,
+ nsIGlobalObject* aGlobal,
+ Blob& aBlob,
+ const Maybe<IntRect>& aCropRect)
+ : mPromise(aPromise),
+ mGlobalObject(aGlobal),
+ mBlob(&aBlob),
+ mCropRect(aCropRect)
+ {
+ }
+
+ virtual ~CreateImageBitmapFromBlob()
+ {
+ }
+
+ // Returns true on success, false on failure.
+ bool DoCreateImageBitmapFromBlob()
+ {
+ RefPtr<ImageBitmap> imageBitmap = CreateImageBitmap();
+
+ // handle errors while creating ImageBitmap
+ // (1) error occurs during reading of the object
+ // (2) the image data is not in a supported file format
+ // (3) the image data is corrupted
+ // All these three cases should reject the promise with "InvalidStateError"
+ // DOMException
+ if (!imageBitmap) {
+ return false;
+ }
+
+ if (imageBitmap && mCropRect.isSome()) {
+ ErrorResult rv;
+ imageBitmap->SetPictureRect(mCropRect.ref(), rv);
+
+ if (rv.Failed()) {
+ mPromise->MaybeReject(rv);
+ return false;
+ }
+ }
+
+ mPromise->MaybeResolve(imageBitmap);
+ return true;
+ }
+
+ // Will return null on failure. In that case, mPromise will already
+ // be rejected with the right thing.
+ virtual already_AddRefed<ImageBitmap> CreateImageBitmap() = 0;
+
+ RefPtr<Promise> mPromise;
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+ RefPtr<mozilla::dom::Blob> mBlob;
+ Maybe<IntRect> mCropRect;
+};
+
+class CreateImageBitmapFromBlobTask final : public Runnable,
+ public CreateImageBitmapFromBlob
+{
+public:
+ CreateImageBitmapFromBlobTask(Promise* aPromise,
+ nsIGlobalObject* aGlobal,
+ Blob& aBlob,
+ const Maybe<IntRect>& aCropRect)
+ :CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ DoCreateImageBitmapFromBlob();
+ return NS_OK;
+ }
+
+private:
+ already_AddRefed<ImageBitmap> CreateImageBitmap() override
+ {
+ // _sourceSize_ is used to get the original size of the source image,
+ // before being cropped.
+ IntSize sourceSize;
+
+ // Keep the orignal cropping rectangle because the mCropRect might be
+ // modified in DecodeAndCropBlob().
+ Maybe<IntRect> originalCropRect = mCropRect;
+
+ RefPtr<layers::Image> data = DecodeAndCropBlob(*mBlob, mCropRect, sourceSize);
+
+ if (NS_WARN_IF(!data)) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Create ImageBitmap object.
+ RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ imageBitmap->SetIsCroppingAreaOutSideOfSourceImage(sourceSize, originalCropRect);
+
+ return imageBitmap.forget();
+ }
+};
+
+class CreateImageBitmapFromBlobWorkerTask final : public WorkerSameThreadRunnable,
+ public CreateImageBitmapFromBlob
+{
+ // This is a synchronous task.
+ class DecodeBlobInMainThreadSyncTask final : public WorkerMainThreadRunnable
+ {
+ public:
+ DecodeBlobInMainThreadSyncTask(WorkerPrivate* aWorkerPrivate,
+ Blob& aBlob,
+ Maybe<IntRect>& aCropRect,
+ layers::Image** aImage,
+ IntSize& aSourceSize)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("ImageBitmap :: Create Image from Blob"))
+ , mBlob(aBlob)
+ , mCropRect(aCropRect)
+ , mImage(aImage)
+ , mSourceSize(aSourceSize)
+ {
+ }
+
+ bool MainThreadRun() override
+ {
+ RefPtr<layers::Image> image = DecodeAndCropBlob(mBlob, mCropRect, mSourceSize);
+
+ if (NS_WARN_IF(!image)) {
+ return true;
+ }
+
+ image.forget(mImage);
+
+ return true;
+ }
+
+ private:
+ Blob& mBlob;
+ Maybe<IntRect>& mCropRect;
+ layers::Image** mImage;
+ IntSize mSourceSize;
+ };
+
+public:
+ CreateImageBitmapFromBlobWorkerTask(Promise* aPromise,
+ nsIGlobalObject* aGlobal,
+ mozilla::dom::Blob& aBlob,
+ const Maybe<IntRect>& aCropRect)
+ : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
+ CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect)
+ {
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ return DoCreateImageBitmapFromBlob();
+ }
+
+private:
+ already_AddRefed<ImageBitmap> CreateImageBitmap() override
+ {
+ // _sourceSize_ is used to get the original size of the source image,
+ // before being cropped.
+ IntSize sourceSize;
+
+ // Keep the orignal cropping rectangle because the mCropRect might be
+ // modified in DecodeAndCropBlob().
+ Maybe<IntRect> originalCropRect = mCropRect;
+
+ RefPtr<layers::Image> data;
+
+ ErrorResult rv;
+ RefPtr<DecodeBlobInMainThreadSyncTask> task =
+ new DecodeBlobInMainThreadSyncTask(mWorkerPrivate, *mBlob, mCropRect,
+ getter_AddRefs(data), sourceSize);
+ task->Dispatch(rv); // This is a synchronous call.
+
+ if (NS_WARN_IF(rv.Failed())) {
+ // XXXbz does this really make sense if we're shutting down? Ah, well.
+ mPromise->MaybeReject(rv);
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!data)) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Create ImageBitmap object.
+ RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ imageBitmap->SetIsCroppingAreaOutSideOfSourceImage(sourceSize, originalCropRect);
+
+ return imageBitmap.forget();
+ }
+
+};
+
+static void
+AsyncCreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal,
+ Blob& aBlob, const Maybe<IntRect>& aCropRect)
+{
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task =
+ new CreateImageBitmapFromBlobTask(aPromise, aGlobal, aBlob, aCropRect);
+ NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
+ } else {
+ RefPtr<CreateImageBitmapFromBlobWorkerTask> task =
+ new CreateImageBitmapFromBlobWorkerTask(aPromise, aGlobal, aBlob, aCropRect);
+ task->Dispatch(); // Actually, to the current worker-thread.
+ }
+}
+
+/* static */ already_AddRefed<Promise>
+ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
+ const Maybe<gfx::IntRect>& aCropRect, ErrorResult& aRv)
+{
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return promise.forget();
+ }
+
+ RefPtr<ImageBitmap> imageBitmap;
+
+ if (aSrc.IsHTMLImageElement()) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Creating ImageBitmap from HTMLImageElement off the main thread.");
+ imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLImageElement(), aCropRect, aRv);
+ } else if (aSrc.IsHTMLVideoElement()) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Creating ImageBitmap from HTMLVideoElement off the main thread.");
+ imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(), aCropRect, aRv);
+ } else if (aSrc.IsHTMLCanvasElement()) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Creating ImageBitmap from HTMLCanvasElement off the main thread.");
+ imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(), aCropRect, aRv);
+ } else if (aSrc.IsImageData()) {
+ imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect, aRv);
+ } else if (aSrc.IsCanvasRenderingContext2D()) {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "Creating ImageBitmap from CanvasRenderingContext2D off the main thread.");
+ imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(), aCropRect, aRv);
+ } else if (aSrc.IsImageBitmap()) {
+ imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect, aRv);
+ } else if (aSrc.IsBlob()) {
+ AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(), aCropRect);
+ return promise.forget();
+ } else {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ if (!aRv.Failed()) {
+ AsyncFulfillImageBitmapPromise(promise, imageBitmap);
+ }
+
+ return promise.forget();
+}
+
+/*static*/ JSObject*
+ImageBitmap::ReadStructuredClone(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ nsIGlobalObject* aParent,
+ const nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
+ uint32_t aIndex)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aReader);
+ // aParent might be null.
+
+ uint32_t picRectX_;
+ uint32_t picRectY_;
+ uint32_t picRectWidth_;
+ uint32_t picRectHeight_;
+ uint32_t isPremultipliedAlpha_;
+ uint32_t isCroppingAreaOutSideOfSourceImage_;
+
+ if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
+ !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
+ !JS_ReadUint32Pair(aReader, &isPremultipliedAlpha_,
+ &isCroppingAreaOutSideOfSourceImage_)) {
+ return nullptr;
+ }
+
+ int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
+ int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
+ int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
+ int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
+
+ // Create a new ImageBitmap.
+ MOZ_ASSERT(!aClonedSurfaces.IsEmpty());
+ MOZ_ASSERT(aIndex < aClonedSurfaces.Length());
+
+ // RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
+ // called because the static analysis thinks dereferencing XPCOM objects
+ // can GC (because in some cases it can!), and a return statement with a
+ // JSObject* type means that JSObject* is on the stack as a raw pointer
+ // while destructors are running.
+ JS::Rooted<JS::Value> value(aCx);
+ {
+ RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
+ RefPtr<ImageBitmap> imageBitmap =
+ new ImageBitmap(aParent, img, isPremultipliedAlpha_);
+
+ imageBitmap->mIsCroppingAreaOutSideOfSourceImage =
+ isCroppingAreaOutSideOfSourceImage_;
+
+ ErrorResult error;
+ imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
+ picRectWidth, picRectHeight), error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ return nullptr;
+ }
+
+ if (!GetOrCreateDOMReflector(aCx, imageBitmap, &value)) {
+ return nullptr;
+ }
+ }
+
+ return &(value.toObject());
+}
+
+/*static*/ bool
+ImageBitmap::WriteStructuredClone(JSStructuredCloneWriter* aWriter,
+ nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
+ ImageBitmap* aImageBitmap)
+{
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aImageBitmap);
+
+ const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
+ const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
+ const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
+ const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
+ const uint32_t isPremultipliedAlpha = aImageBitmap->mIsPremultipliedAlpha ? 1 : 0;
+ const uint32_t isCroppingAreaOutSideOfSourceImage = aImageBitmap->mIsCroppingAreaOutSideOfSourceImage ? 1 : 0;
+
+ // Indexing the cloned surfaces and send the index to the receiver.
+ uint32_t index = aClonedSurfaces.Length();
+
+ if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
+ NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
+ NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight)) ||
+ NS_WARN_IF(!JS_WriteUint32Pair(aWriter, isPremultipliedAlpha,
+ isCroppingAreaOutSideOfSourceImage))) {
+ return false;
+ }
+
+ RefPtr<SourceSurface> surface =
+ aImageBitmap->mData->GetAsSourceSurface();
+ RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
+ RefPtr<DataSourceSurface> dstDataSurface;
+ {
+ // DataSourceSurfaceD2D1::GetStride() will call EnsureMapped implicitly and
+ // won't Unmap after exiting function. So instead calling GetStride()
+ // directly, using ScopedMap to get stride.
+ DataSourceSurface::ScopedMap map(snapshot, DataSourceSurface::READ);
+ dstDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(snapshot->GetSize(),
+ snapshot->GetFormat(),
+ map.GetStride(),
+ true);
+ }
+ MOZ_ASSERT(dstDataSurface);
+ Factory::CopyDataSourceSurface(snapshot, dstDataSurface);
+ aClonedSurfaces.AppendElement(dstDataSurface);
+ return true;
+}
+
+/*static*/ bool
+ImageBitmap::ExtensionsEnabled(JSContext* aCx, JSObject*)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("canvas.imagebitmap_extensions.enabled");
+ } else {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+ return workerPrivate->ImageBitmapExtensionsEnabled();
+ }
+}
+
+// ImageBitmap extensions.
+ImageBitmapFormat
+ImageBitmap::FindOptimalFormat(const Optional<Sequence<ImageBitmapFormat>>& aPossibleFormats,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDataWrapper, "No ImageBitmapFormatUtils functionalities.");
+
+ ImageBitmapFormat platformFormat = mDataWrapper->GetFormat();
+
+ if (!aPossibleFormats.WasPassed() ||
+ aPossibleFormats.Value().Contains(platformFormat)) {
+ return platformFormat;
+ } else {
+ // If no matching is found, FindBestMatchingFromat() returns
+ // ImageBitmapFormat::EndGuard_ and we throw an exception.
+ ImageBitmapFormat optimalFormat =
+ FindBestMatchingFromat(platformFormat, aPossibleFormats.Value());
+
+ if (optimalFormat == ImageBitmapFormat::EndGuard_) {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ }
+
+ return optimalFormat;
+ }
+}
+
+int32_t
+ImageBitmap::MappedDataLength(ImageBitmapFormat aFormat, ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDataWrapper, "No ImageBitmapFormatUtils functionalities.");
+
+ if (aFormat == mDataWrapper->GetFormat()) {
+ return mDataWrapper->GetBufferLength();
+ } else {
+ return CalculateImageBufferSize(aFormat, Width(), Height());
+ }
+}
+
+template<typename T>
+class MapDataIntoBufferSource
+{
+protected:
+ MapDataIntoBufferSource(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const T& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+ : mPromise(aPromise)
+ , mImageBitmap(aImageBitmap)
+ , mBuffer(aCx, aBuffer.Obj())
+ , mOffset(aOffset)
+ , mFormat(aFormat)
+ {
+ MOZ_ASSERT(mPromise);
+ MOZ_ASSERT(JS_IsArrayBufferObject(mBuffer) ||
+ JS_IsArrayBufferViewObject(mBuffer));
+ }
+
+ virtual ~MapDataIntoBufferSource() = default;
+
+ void DoMapDataIntoBufferSource()
+ {
+ ErrorResult error;
+
+ // Prepare destination buffer.
+ uint8_t* bufferData = nullptr;
+ uint32_t bufferLength = 0;
+ bool isSharedMemory = false;
+ if (JS_IsArrayBufferObject(mBuffer)) {
+ js::GetArrayBufferLengthAndData(mBuffer, &bufferLength, &isSharedMemory, &bufferData);
+ } else if (JS_IsArrayBufferViewObject(mBuffer)) {
+ js::GetArrayBufferViewLengthAndData(mBuffer, &bufferLength, &isSharedMemory, &bufferData);
+ } else {
+ error.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ if (NS_WARN_IF(!bufferData) || NS_WARN_IF(!bufferLength)) {
+ error.Throw(NS_ERROR_NOT_AVAILABLE);
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ // Check length.
+ const int32_t neededBufferLength =
+ mImageBitmap->MappedDataLength(mFormat, error);
+
+ if (((int32_t)bufferLength - mOffset) < neededBufferLength) {
+ error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ // Call ImageBitmapFormatUtils.
+ UniquePtr<ImagePixelLayout> layout =
+ mImageBitmap->mDataWrapper->MapDataInto(bufferData,
+ mOffset,
+ bufferLength,
+ mFormat,
+ error);
+
+ if (NS_WARN_IF(!layout)) {
+ mPromise->MaybeReject(error);
+ return;
+ }
+
+ mPromise->MaybeResolve(*layout);
+ }
+
+ RefPtr<Promise> mPromise;
+ RefPtr<ImageBitmap> mImageBitmap;
+ JS::PersistentRooted<JSObject*> mBuffer;
+ int32_t mOffset;
+ ImageBitmapFormat mFormat;
+};
+
+template<typename T>
+class MapDataIntoBufferSourceTask final : public Runnable,
+ public MapDataIntoBufferSource<T>
+{
+public:
+ MapDataIntoBufferSourceTask(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const T& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+ : MapDataIntoBufferSource<T>(aCx, aPromise, aImageBitmap, aBuffer, aOffset, aFormat)
+ {
+ }
+
+ virtual ~MapDataIntoBufferSourceTask() = default;
+
+ NS_IMETHOD Run() override
+ {
+ MapDataIntoBufferSource<T>::DoMapDataIntoBufferSource();
+ return NS_OK;
+ }
+};
+
+template<typename T>
+class MapDataIntoBufferSourceWorkerTask final : public WorkerSameThreadRunnable,
+ public MapDataIntoBufferSource<T>
+{
+public:
+ MapDataIntoBufferSourceWorkerTask(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const T& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+ : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
+ MapDataIntoBufferSource<T>(aCx, aPromise, aImageBitmap, aBuffer, aOffset, aFormat)
+ {
+ }
+
+ virtual ~MapDataIntoBufferSourceWorkerTask() = default;
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MapDataIntoBufferSource<T>::DoMapDataIntoBufferSource();
+ return true;
+ }
+};
+
+void AsyncMapDataIntoBufferSource(JSContext* aCx,
+ Promise *aPromise,
+ ImageBitmap *aImageBitmap,
+ const ArrayBufferViewOrArrayBuffer& aBuffer,
+ int32_t aOffset,
+ ImageBitmapFormat aFormat)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aPromise);
+ MOZ_ASSERT(aImageBitmap);
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task;
+
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ task = new MapDataIntoBufferSourceTask<ArrayBuffer>(aCx, aPromise, aImageBitmap, buffer, aOffset, aFormat);
+ } else if (aBuffer.IsArrayBufferView()) {
+ const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView();
+ task = new MapDataIntoBufferSourceTask<ArrayBufferView>(aCx, aPromise, aImageBitmap, bufferView, aOffset, aFormat);
+ }
+
+ NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
+ } else {
+ RefPtr<WorkerSameThreadRunnable> task;
+
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ task = new MapDataIntoBufferSourceWorkerTask<ArrayBuffer>(aCx, aPromise, aImageBitmap, buffer, aOffset, aFormat);
+ } else if (aBuffer.IsArrayBufferView()) {
+ const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView();
+ task = new MapDataIntoBufferSourceWorkerTask<ArrayBufferView>(aCx, aPromise, aImageBitmap, bufferView, aOffset, aFormat);
+ }
+
+ task->Dispatch(); // Actually, to the current worker-thread.
+ }
+}
+
+already_AddRefed<Promise>
+ImageBitmap::MapDataInto(JSContext* aCx,
+ ImageBitmapFormat aFormat,
+ const ArrayBufferViewOrArrayBuffer& aBuffer,
+ int32_t aOffset, ErrorResult& aRv)
+{
+ MOZ_ASSERT(mDataWrapper, "No ImageBitmapFormatUtils functionalities.");
+ MOZ_ASSERT(aCx, "No JSContext while calling ImageBitmap::MapDataInto().");
+
+ RefPtr<Promise> promise = Promise::Create(mParent, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Check for cases that should throws.
+ // Case 1:
+ // If image bitmap was cropped to the source rectangle so that it contains any
+ // transparent black pixels (cropping area is outside of the source image),
+ // then reject promise with IndexSizeError and abort these steps.
+ if (mIsCroppingAreaOutSideOfSourceImage) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return promise.forget();
+ }
+
+ // Case 2:
+ // If the image bitmap is going to be accessed in YUV422/YUV422 series with a
+ // cropping area starts at an odd x or y coordinate.
+ if (aFormat == ImageBitmapFormat::YUV422P ||
+ aFormat == ImageBitmapFormat::YUV420P ||
+ aFormat == ImageBitmapFormat::YUV420SP_NV12 ||
+ aFormat == ImageBitmapFormat::YUV420SP_NV21) {
+ if ((mPictureRect.x & 1) || (mPictureRect.y & 1)) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return promise.forget();
+ }
+ }
+
+ AsyncMapDataIntoBufferSource(aCx, promise, this, aBuffer, aOffset, aFormat);
+ return promise.forget();
+}
+
+// ImageBitmapFactories extensions.
+static SurfaceFormat
+ImageFormatToSurfaceFromat(mozilla::dom::ImageBitmapFormat aFormat)
+{
+ switch(aFormat) {
+ case ImageBitmapFormat::RGBA32:
+ return SurfaceFormat::R8G8B8A8;
+ case ImageBitmapFormat::BGRA32:
+ return SurfaceFormat::B8G8R8A8;
+ case ImageBitmapFormat::RGB24:
+ return SurfaceFormat::R8G8B8;
+ case ImageBitmapFormat::BGR24:
+ return SurfaceFormat::B8G8R8;
+ case ImageBitmapFormat::GRAY8:
+ return SurfaceFormat::A8;
+ case ImageBitmapFormat::HSV:
+ return SurfaceFormat::HSV;
+ case ImageBitmapFormat::Lab:
+ return SurfaceFormat::Lab;
+ case ImageBitmapFormat::DEPTH:
+ return SurfaceFormat::Depth;
+ default:
+ return SurfaceFormat::UNKNOWN;
+ }
+}
+
+static already_AddRefed<layers::Image>
+CreateImageFromBufferSourceRawData(const uint8_t*aBufferData,
+ uint32_t aBufferLength,
+ mozilla::dom::ImageBitmapFormat aFormat,
+ const Sequence<ChannelPixelLayout>& aLayout)
+{
+ MOZ_ASSERT(aBufferData);
+ MOZ_ASSERT(aBufferLength > 0);
+
+ switch(aFormat) {
+ case ImageBitmapFormat::RGBA32:
+ case ImageBitmapFormat::BGRA32:
+ case ImageBitmapFormat::RGB24:
+ case ImageBitmapFormat::BGR24:
+ case ImageBitmapFormat::GRAY8:
+ case ImageBitmapFormat::HSV:
+ case ImageBitmapFormat::Lab:
+ case ImageBitmapFormat::DEPTH:
+ {
+ const nsTArray<ChannelPixelLayout>& channels = aLayout;
+ MOZ_ASSERT(channels.Length() != 0, "Empty Channels.");
+
+ const SurfaceFormat srcFormat = ImageFormatToSurfaceFromat(aFormat);
+ const uint32_t srcStride = channels[0].mStride;
+ const IntSize srcSize(channels[0].mWidth, channels[0].mHeight);
+
+ RefPtr<DataSourceSurface> dstDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(srcSize, srcFormat, srcStride);
+
+ if (NS_WARN_IF(!dstDataSurface)) {
+ return nullptr;
+ }
+
+ // Copy the raw data into the newly created DataSourceSurface.
+ DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE);
+ if (NS_WARN_IF(!dstMap.IsMapped())) {
+ return nullptr;
+ }
+
+ const uint8_t* srcBufferPtr = aBufferData;
+ uint8_t* dstBufferPtr = dstMap.GetData();
+
+ for (int i = 0; i < srcSize.height; ++i) {
+ memcpy(dstBufferPtr, srcBufferPtr, srcStride);
+ srcBufferPtr += srcStride;
+ dstBufferPtr += dstMap.GetStride();
+ }
+
+ // Create an Image from the BGRA SourceSurface.
+ RefPtr<SourceSurface> surface = dstDataSurface;
+ RefPtr<layers::Image> image = CreateImageFromSurface(surface);
+
+ if (NS_WARN_IF(!image)) {
+ return nullptr;
+ }
+
+ return image.forget();
+ }
+ case ImageBitmapFormat::YUV444P:
+ case ImageBitmapFormat::YUV422P:
+ case ImageBitmapFormat::YUV420P:
+ case ImageBitmapFormat::YUV420SP_NV12:
+ case ImageBitmapFormat::YUV420SP_NV21:
+ {
+ // Prepare the PlanarYCbCrData.
+ const ChannelPixelLayout& yLayout = aLayout[0];
+ const ChannelPixelLayout& uLayout = aFormat != ImageBitmapFormat::YUV420SP_NV21 ? aLayout[1] : aLayout[2];
+ const ChannelPixelLayout& vLayout = aFormat != ImageBitmapFormat::YUV420SP_NV21 ? aLayout[2] : aLayout[1];
+
+ layers::PlanarYCbCrData data;
+
+ // Luminance buffer
+ data.mYChannel = const_cast<uint8_t*>(aBufferData + yLayout.mOffset);
+ data.mYStride = yLayout.mStride;
+ data.mYSize = gfx::IntSize(yLayout.mWidth, yLayout.mHeight);
+ data.mYSkip = yLayout.mSkip;
+
+ // Chroma buffers
+ data.mCbChannel = const_cast<uint8_t*>(aBufferData + uLayout.mOffset);
+ data.mCrChannel = const_cast<uint8_t*>(aBufferData + vLayout.mOffset);
+ data.mCbCrStride = uLayout.mStride;
+ data.mCbCrSize = gfx::IntSize(uLayout.mWidth, uLayout.mHeight);
+ data.mCbSkip = uLayout.mSkip;
+ data.mCrSkip = vLayout.mSkip;
+
+ // Picture rectangle.
+ // We set the picture rectangle to exactly the size of the source image to
+ // keep the full original data.
+ data.mPicX = 0;
+ data.mPicY = 0;
+ data.mPicSize = data.mYSize;
+
+ // Create a layers::Image and set data.
+ if (aFormat == ImageBitmapFormat::YUV444P ||
+ aFormat == ImageBitmapFormat::YUV422P ||
+ aFormat == ImageBitmapFormat::YUV420P) {
+ RefPtr<layers::PlanarYCbCrImage> image =
+ new layers::RecyclingPlanarYCbCrImage(new layers::BufferRecycleBin());
+
+ if (NS_WARN_IF(!image)) {
+ return nullptr;
+ }
+
+ // Set Data.
+ if (NS_WARN_IF(!image->CopyData(data))) {
+ return nullptr;
+ }
+
+ return image.forget();
+ } else {
+ RefPtr<layers::NVImage>image = new layers::NVImage();
+
+ if (NS_WARN_IF(!image)) {
+ return nullptr;
+ }
+
+ // Set Data.
+ if (NS_WARN_IF(!image->SetData(data))) {
+ return nullptr;
+ }
+
+ return image.forget();
+ }
+ }
+ default:
+ return nullptr;
+ }
+}
+
+/*
+ * This is a synchronous task.
+ * This class is used to create a layers::CairoImage from raw data in the main
+ * thread. While creating an ImageBitmap from an BufferSource, we need to create
+ * a SouceSurface from the BufferSource raw data and then set the SourceSurface
+ * into a layers::CairoImage. However, the layers::CairoImage asserts the
+ * setting operation in the main thread, so if we are going to create an
+ * ImageBitmap from an BufferSource off the main thread, we post an event to the
+ * main thread to create a layers::CairoImage from an BufferSource raw data.
+ *
+ * TODO: Once the layers::CairoImage is constructible off the main thread, which
+ * means the SouceSurface could be released anywhere, we do not need this
+ * task anymore.
+ */
+class CreateImageFromBufferSourceRawDataInMainThreadSyncTask final :
+ public WorkerMainThreadRunnable
+{
+public:
+ CreateImageFromBufferSourceRawDataInMainThreadSyncTask(const uint8_t* aBuffer,
+ uint32_t aBufferLength,
+ mozilla::dom::ImageBitmapFormat aFormat,
+ const Sequence<ChannelPixelLayout>& aLayout,
+ /*output*/ layers::Image** aImage)
+ : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(),
+ NS_LITERAL_CSTRING("ImageBitmap-extensions :: Create Image from BufferSource Raw Data"))
+ , mImage(aImage)
+ , mBuffer(aBuffer)
+ , mBufferLength(aBufferLength)
+ , mFormat(aFormat)
+ , mLayout(aLayout)
+ {
+ MOZ_ASSERT(!(*aImage), "Don't pass an existing Image into CreateImageFromBufferSourceRawDataInMainThreadSyncTask.");
+ }
+
+ bool MainThreadRun() override
+ {
+ RefPtr<layers::Image> image =
+ CreateImageFromBufferSourceRawData(mBuffer, mBufferLength, mFormat, mLayout);
+
+ if (NS_WARN_IF(!image)) {
+ return true;
+ }
+
+ image.forget(mImage);
+
+ return true;
+ }
+
+private:
+ layers::Image** mImage;
+ const uint8_t* mBuffer;
+ uint32_t mBufferLength;
+ mozilla::dom::ImageBitmapFormat mFormat;
+ const Sequence<ChannelPixelLayout>& mLayout;
+};
+
+/*static*/ already_AddRefed<Promise>
+ImageBitmap::Create(nsIGlobalObject* aGlobal,
+ const ImageBitmapSource& aBuffer,
+ int32_t aOffset, int32_t aLength,
+ mozilla::dom::ImageBitmapFormat aFormat,
+ const Sequence<ChannelPixelLayout>& aLayout,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ uint8_t* bufferData = nullptr;
+ uint32_t bufferLength = 0;
+
+ if (aBuffer.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aBuffer.GetAsArrayBuffer();
+ buffer.ComputeLengthAndData();
+ bufferData = buffer.Data();
+ bufferLength = buffer.Length();
+ } else if (aBuffer.IsArrayBufferView()) {
+ const ArrayBufferView& bufferView = aBuffer.GetAsArrayBufferView();
+ bufferView.ComputeLengthAndData();
+ bufferData = bufferView.Data();
+ bufferLength = bufferView.Length();
+ } else {
+ aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+ return promise.forget();
+ }
+
+ MOZ_ASSERT(bufferData && bufferLength > 0, "Cannot read data from BufferSource.");
+
+ // Check the buffer.
+ if (((uint32_t)(aOffset + aLength) > bufferLength)) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return promise.forget();
+ }
+
+ // Create and Crop the raw data into a layers::Image
+ RefPtr<layers::Image> data;
+ if (NS_IsMainThread()) {
+ data = CreateImageFromBufferSourceRawData(bufferData + aOffset, bufferLength,
+ aFormat, aLayout);
+ } else {
+ RefPtr<CreateImageFromBufferSourceRawDataInMainThreadSyncTask> task =
+ new CreateImageFromBufferSourceRawDataInMainThreadSyncTask(bufferData + aOffset,
+ bufferLength,
+ aFormat,
+ aLayout,
+ getter_AddRefs(data));
+ task->Dispatch(aRv);
+ }
+
+ if (NS_WARN_IF(!data)) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return promise.forget();
+ }
+
+ // Create an ImageBimtap.
+ // Assume the data from an external buffer is not alpha-premultiplied.
+ RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, false);
+
+ // We don't need to call SetPictureRect() here because there is no cropping
+ // supported and the ImageBitmap's mPictureRect is the size of the source
+ // image in default
+
+ // We don't need to set mIsCroppingAreaOutSideOfSourceImage here because there
+ // is no cropping supported and the mIsCroppingAreaOutSideOfSourceImage is
+ // false in default.
+
+ AsyncFulfillImageBitmapPromise(promise, imageBitmap);
+
+ return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla