summaryrefslogtreecommitdiffstats
path: root/dom/canvas/ImageBitmap.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/canvas/ImageBitmap.cpp1700
1 files changed, 1700 insertions, 0 deletions
diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp
new file mode 100644
index 0000000000..da8f3d1201
--- /dev/null
+++ b/dom/canvas/ImageBitmap.cpp
@@ -0,0 +1,1700 @@
+/* -*- 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/BlobImpl.h"
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+#include "mozilla/dom/CanvasUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLMediaElementBinding.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/OffscreenCanvas.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/SVGImageElement.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/ScopeExit.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIAsyncInputStream.h"
+#include "nsNetUtil.h"
+#include "nsLayoutUtils.h"
+#include "nsStreamUtils.h"
+#include "ImageUtils.h"
+#include "imgLoader.h"
+#include "imgTools.h"
+
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
+using mozilla::dom::HTMLMediaElement_Binding::NETWORK_EMPTY;
+
+namespace mozilla::dom {
+
+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
+
+/* This class observes shutdown notifications and sends that notification
+ * to the worker thread if the image bitmap is on a worker thread.
+ */
+class ImageBitmapShutdownObserver final : public nsIObserver {
+ public:
+ explicit ImageBitmapShutdownObserver(ImageBitmap* aImageBitmap)
+ : mImageBitmap(nullptr) {
+ if (NS_IsMainThread()) {
+ mImageBitmap = aImageBitmap;
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ mMainThreadEventTarget = workerPrivate->MainThreadEventTarget();
+ mSendToWorkerTask = new SendShutdownToWorkerThread(aImageBitmap);
+ }
+ }
+
+ void RegisterObserver() {
+ if (NS_IsMainThread()) {
+ nsContentUtils::RegisterShutdownObserver(this);
+ return;
+ }
+
+ MOZ_ASSERT(mMainThreadEventTarget);
+ RefPtr<ImageBitmapShutdownObserver> self = this;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("ImageBitmapShutdownObserver::RegisterObserver",
+ [self]() { self->RegisterObserver(); });
+
+ mMainThreadEventTarget->Dispatch(r.forget());
+ }
+
+ void UnregisterObserver() {
+ if (NS_IsMainThread()) {
+ nsContentUtils::UnregisterShutdownObserver(this);
+ return;
+ }
+
+ MOZ_ASSERT(mMainThreadEventTarget);
+ RefPtr<ImageBitmapShutdownObserver> self = this;
+ nsCOMPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("ImageBitmapShutdownObserver::RegisterObserver",
+ [self]() { self->UnregisterObserver(); });
+
+ mMainThreadEventTarget->Dispatch(r.forget());
+ }
+
+ void Clear() {
+ mImageBitmap = nullptr;
+ if (mSendToWorkerTask) {
+ mSendToWorkerTask->mImageBitmap = nullptr;
+ }
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ private:
+ ~ImageBitmapShutdownObserver() = default;
+
+ class SendShutdownToWorkerThread : public MainThreadWorkerControlRunnable {
+ public:
+ explicit SendShutdownToWorkerThread(ImageBitmap* aImageBitmap)
+ : MainThreadWorkerControlRunnable(GetCurrentThreadWorkerPrivate()),
+ mImageBitmap(aImageBitmap) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ if (mImageBitmap) {
+ mImageBitmap->OnShutdown();
+ mImageBitmap = nullptr;
+ }
+ return true;
+ }
+
+ ImageBitmap* mImageBitmap;
+ };
+
+ ImageBitmap* mImageBitmap;
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+ RefPtr<SendShutdownToWorkerThread> mSendToWorkerTask;
+};
+
+NS_IMPL_ISUPPORTS(ImageBitmapShutdownObserver, nsIObserver)
+
+NS_IMETHODIMP
+ImageBitmapShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ if (mSendToWorkerTask) {
+ mSendToWorkerTask->Dispatch();
+ } else {
+ if (mImageBitmap) {
+ mImageBitmap->OnShutdown();
+ mImageBitmap = nullptr;
+ }
+ }
+ nsContentUtils::UnregisterShutdownObserver(this);
+ }
+
+ return NS_OK;
+}
+
+/*
+ * 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();
+ DataSourceSurface::ScopedMap rgbaMap(rgbaDataSurface,
+ DataSourceSurface::READ);
+ if (NS_WARN_IF(!rgbaMap.IsMapped())) {
+ return nullptr;
+ }
+
+ RefPtr<DataSourceSurface> bgraDataSurface =
+ Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(),
+ SurfaceFormat::B8G8R8A8,
+ rgbaMap.GetStride());
+ if (NS_WARN_IF(!bgraDataSurface)) {
+ return nullptr;
+ }
+
+ DataSourceSurface::ScopedMap bgraMap(bgraDataSurface,
+ DataSourceSurface::WRITE);
+ if (NS_WARN_IF(!bgraMap.IsMapped())) {
+ return nullptr;
+ }
+
+ SwizzleData(rgbaMap.GetData(), rgbaMap.GetStride(), SurfaceFormat::R8G8B8A8,
+ bgraMap.GetData(), bgraMap.GetStride(), SurfaceFormat::B8G8R8A8,
+ bgraDataSurface->GetSize());
+
+ // Create an Image from the BGRA SourceSurface.
+ return CreateImageFromSurface(bgraDataSurface);
+}
+
+/*
+ * 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(),
+ "ImageBitmap :: Create Image from Raw Data"_ns),
+ 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;
+};
+
+/*
+ * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the
+ * security checking.
+ */
+template <class ElementType>
+static already_AddRefed<SourceSurface> GetSurfaceFromElement(
+ nsIGlobalObject* aGlobal, ElementType& aElement, bool* aWriteOnly,
+ ErrorResult& aRv) {
+ SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromElement(
+ &aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE);
+
+ RefPtr<SourceSurface> surface = res.GetSourceSurface();
+ if (NS_WARN_IF(!surface)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ *aWriteOnly = res.mIsWriteOnly;
+
+ return surface.forget();
+}
+
+ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
+ bool aWriteOnly, gfxAlphaType aAlphaType)
+ : mParent(aGlobal),
+ mData(aData),
+ mSurface(nullptr),
+ mDataWrapper(new ImageUtils(mData)),
+ mPictureRect(aData->GetPictureRect()),
+ mAlphaType(aAlphaType),
+ mAllocatedImageData(false),
+ mWriteOnly(aWriteOnly) {
+ MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
+
+ mShutdownObserver = new ImageBitmapShutdownObserver(this);
+ mShutdownObserver->RegisterObserver();
+}
+
+ImageBitmap::~ImageBitmap() {
+ if (mShutdownObserver) {
+ mShutdownObserver->Clear();
+ mShutdownObserver->UnregisterObserver();
+ mShutdownObserver = nullptr;
+ }
+}
+
+JSObject* ImageBitmap::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ImageBitmap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ImageBitmap::Close() {
+ mData = nullptr;
+ mSurface = nullptr;
+ mDataWrapper = nullptr;
+ mPictureRect.SetEmpty();
+}
+
+void ImageBitmap::OnShutdown() {
+ mShutdownObserver = nullptr;
+
+ Close();
+}
+
+void ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv) {
+ mPictureRect = FixUpNegativeDimension(aRect, aRv);
+}
+
+/*
+ * 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;
+ }
+ }
+
+ 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.
+ // Ignore this step if the source surface does not have alpha channel; this
+ // kind of source surfaces might come form layers::PlanarYCbCrImage.
+ if (mAlphaType == gfxAlphaType::NonPremult &&
+ !IsOpaque(mSurface->GetFormat())) {
+ 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)) {
+ srcMap = dstMap;
+ } else {
+ 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;
+ }
+ }
+
+ PremultiplyData(srcMap.mData, srcMap.mStride, mSurface->GetFormat(),
+ dstMap.mData, dstMap.mStride, mSurface->GetFormat(),
+ dstSurface->GetSize());
+
+ dstSurface->Unmap();
+ if (srcSurface) {
+ srcSurface->Unmap();
+ }
+
+ mAlphaType = gfxAlphaType::Premult;
+ 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 {
+ if (!mData) {
+ // A closed image cannot be cloned.
+ return nullptr;
+ }
+
+ UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
+ result->mPictureRect = mPictureRect;
+ result->mAlphaType = mAlphaType;
+ RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
+ if (!surface) {
+ // It might just not be possible to get/map the surface. (e.g. from another
+ // process)
+ return nullptr;
+ }
+
+ result->mSurface = surface->GetDataSurface();
+ MOZ_ASSERT(result->mSurface);
+ result->mWriteOnly = mWriteOnly;
+
+ return result;
+}
+
+/* static */
+already_AddRefed<ImageBitmap> ImageBitmap::CreateFromSourceSurface(
+ nsIGlobalObject* aGlobal, gfx::SourceSurface* aSource, ErrorResult& aRv) {
+ RefPtr<layers::Image> data = CreateImageFromSurface(aSource);
+ RefPtr<ImageBitmap> ret =
+ new ImageBitmap(aGlobal, data, false /* writeOnly */);
+ ret->mAllocatedImageData = true;
+ return ret.forget();
+}
+
+/* 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->mWriteOnly, aData->mAlphaType);
+
+ ret->mAllocatedImageData = true;
+
+ ErrorResult rv;
+ ret->SetPictureRect(aData->mPictureRect, rv);
+ return ret.forget();
+}
+
+/* static */
+already_AddRefed<ImageBitmap> ImageBitmap::CreateFromOffscreenCanvas(
+ nsIGlobalObject* aGlobal, OffscreenCanvas& aOffscreenCanvas,
+ ErrorResult& aRv) {
+ // Check write-only mode.
+ bool writeOnly = aOffscreenCanvas.IsWriteOnly();
+
+ SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromOffscreenCanvas(
+ &aOffscreenCanvas, nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE);
+
+ 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, writeOnly);
+
+ ret->mAllocatedImageData = true;
+
+ 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;
+ }
+
+ bool writeOnly = true;
+
+ // Get the SourceSurface out from the image element and then do security
+ // checking.
+ RefPtr<SourceSurface> surface =
+ GetSurfaceFromElement(aGlobal, aImageEl, &writeOnly, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Create ImageBitmap.
+ RefPtr<layers::Image> data = CreateImageFromSurface(surface);
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ return ret.forget();
+}
+
+/* static */
+already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
+ nsIGlobalObject* aGlobal, SVGImageElement& aImageEl,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv) {
+ bool writeOnly = true;
+
+ // Get the SourceSurface out from the image element and then do security
+ // checking.
+ RefPtr<SourceSurface> surface =
+ GetSurfaceFromElement(aGlobal, aImageEl, &writeOnly, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Create ImageBitmap.
+ RefPtr<layers::Image> data = CreateImageFromSurface(surface);
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ 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() == 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() <= HAVE_METADATA) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Check security.
+ nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal();
+ bool hadCrossOriginRedirects = aVideoEl.HadCrossOriginRedirects();
+ bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
+ bool writeOnly = CanvasUtils::CheckWriteOnlySecurity(CORSUsed, principal,
+ hadCrossOriginRedirects);
+
+ // Create ImageBitmap.
+ RefPtr<layers::Image> data = aVideoEl.GetCurrentImage();
+ if (!data) {
+ aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+ return nullptr;
+ }
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ 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;
+ }
+
+ bool writeOnly = true;
+ RefPtr<SourceSurface> surface =
+ GetSurfaceFromElement(aGlobal, aCanvasEl, &writeOnly, aRv);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (!writeOnly) {
+ writeOnly = aCanvasEl.IsWriteOnly();
+ }
+
+ // Crop the source surface if needed.
+ RefPtr<SourceSurface> croppedSurface;
+ IntRect cropRect = aCropRect.valueOr(IntRect());
+
+ // If the HTMLCanvasElement's rendering context is WebGL/WebGPU,
+ // 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.
+ bool needToReportMemoryAllocation = false;
+ if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 ||
+ aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2 ||
+ aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGPU) &&
+ aCropRect.isSome()) {
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ croppedSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
+ cropRect.MoveTo(0, 0);
+ needToReportMemoryAllocation = true;
+ } else {
+ croppedSurface = surface;
+ }
+
+ if (NS_WARN_IF(!croppedSurface)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Create an Image from the SourceSurface.
+ RefPtr<layers::Image> data = CreateImageFromSurface(croppedSurface);
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+
+ if (needToReportMemoryAllocation) {
+ ret->mAllocatedImageData = true;
+ }
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(cropRect, aRv);
+ }
+
+ return ret.forget();
+}
+
+/* static */
+already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
+ nsIGlobalObject* aGlobal, ImageData& aImageData,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv) {
+ // Copy data into SourceSurface.
+ RootedSpiderMonkeyInterface<Uint8ClampedArray> array(RootingCx());
+ DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
+ MOZ_ASSERT(inited);
+
+ array.ComputeState();
+ const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
+ // ImageData's underlying data is not alpha-premultiplied.
+ const auto alphaType = gfxAlphaType::NonPremult;
+ 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 the data could move during a GC, copy it out into a local buffer that
+ // lives until a CreateImageFromRawData lower in the stack copies it.
+ // Reassure the static analysis that we know what we're doing.
+ size_t maxInline = JS_MaxMovableTypedArraySize();
+ uint8_t inlineDataBuffer[maxInline];
+ uint8_t* fixedData = array.FixedData(inlineDataBuffer, maxInline);
+
+ // Lie to the hazard analysis and say that we're done with everything that
+ // `array` was using (safe because the data buffer is fixed, and the holding
+ // JSObject is being kept alive elsewhere.)
+ array.Reset();
+
+ if (NS_IsMainThread()) {
+ data = CreateImageFromRawData(imageSize, imageStride, FORMAT, fixedData,
+ dataLength, aCropRect);
+ } else {
+ RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task =
+ new CreateImageFromRawDataInMainThreadSyncTask(
+ fixedData, dataLength, imageStride, FORMAT, imageSize, aCropRect,
+ getter_AddRefs(data));
+ task->Dispatch(Canceling, aRv);
+ }
+
+ if (NS_WARN_IF(!data)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ // Create an ImageBitmap.
+ RefPtr<ImageBitmap> ret =
+ new ImageBitmap(aGlobal, data, false /* write-only */, alphaType);
+
+ ret->mAllocatedImageData = true;
+
+ // The cropping information has been handled in the CreateImageFromRawData()
+ // function.
+
+ return ret.forget();
+}
+
+/* static */
+already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
+ nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
+ const Maybe<IntRect>& aCropRect, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
+ nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(win);
+ if (NS_WARN_IF(!window) || !window->GetExtantDoc()) {
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return nullptr;
+ }
+
+ window->GetExtantDoc()->WarnOnceAbout(
+ DeprecatedOperations::eCreateImageBitmapCanvasRenderingContext2D);
+
+ // Check write-only mode.
+ bool writeOnly =
+ aCanvasCtx.GetCanvas()->IsWriteOnly() || aCanvasCtx.IsWriteOnly();
+
+ 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);
+ RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
+
+ ret->mAllocatedImageData = true;
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ 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_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ RefPtr<layers::Image> data = aImageBitmap.mData;
+ RefPtr<ImageBitmap> ret = new ImageBitmap(
+ aGlobal, data, aImageBitmap.mWriteOnly, aImageBitmap.mAlphaType);
+
+ // Set the picture rectangle.
+ if (ret && aCropRect.isSome()) {
+ ret->SetPictureRect(aCropRect.ref(), aRv);
+ }
+
+ 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)
+ : Runnable("dom::FulfillImageBitmapPromiseTask"),
+ 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.
+ }
+}
+
+class CreateImageBitmapFromBlobRunnable;
+
+class CreateImageBitmapFromBlob final : public DiscardableRunnable,
+ public imgIContainerCallback,
+ public nsIInputStreamCallback {
+ friend class CreateImageBitmapFromBlobRunnable;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_IMGICONTAINERCALLBACK
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ static already_AddRefed<CreateImageBitmapFromBlob> Create(
+ Promise* aPromise, nsIGlobalObject* aGlobal, Blob& aBlob,
+ const Maybe<IntRect>& aCropRect, nsIEventTarget* aMainThreadEventTarget);
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(IsCurrentThread());
+
+ nsresult rv = StartMimeTypeAndDecodeAndCropBlob();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
+ }
+
+ return NS_OK;
+ }
+
+ // Called by the WorkerRef.
+ void WorkerShuttingDown();
+
+ private:
+ CreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal,
+ already_AddRefed<nsIInputStream> aInputStream,
+ const Maybe<IntRect>& aCropRect,
+ nsIEventTarget* aMainThreadEventTarget)
+ : DiscardableRunnable("dom::CreateImageBitmapFromBlob"),
+ mMutex("dom::CreateImageBitmapFromBlob::mMutex"),
+ mPromise(aPromise),
+ mGlobalObject(aGlobal),
+ mInputStream(std::move(aInputStream)),
+ mCropRect(aCropRect),
+ mOriginalCropRect(aCropRect),
+ mMainThreadEventTarget(aMainThreadEventTarget),
+ mThread(PR_GetCurrentThread()) {}
+
+ virtual ~CreateImageBitmapFromBlob() = default;
+
+ bool IsCurrentThread() const { return mThread == PR_GetCurrentThread(); }
+
+ // Called on the owning thread.
+ nsresult StartMimeTypeAndDecodeAndCropBlob();
+
+ // Will be called when the decoding + cropping is completed on the
+ // main-thread. This could the not the owning thread!
+ void MimeTypeAndDecodeAndCropBlobCompletedMainThread(layers::Image* aImage,
+ nsresult aStatus);
+
+ // Will be called when the decoding + cropping is completed on the owning
+ // thread.
+ void MimeTypeAndDecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
+ nsresult aStatus);
+
+ // This is called on the main-thread only.
+ nsresult MimeTypeAndDecodeAndCropBlob();
+
+ // This is called on the main-thread only.
+ nsresult DecodeAndCropBlob(const nsACString& aMimeType);
+
+ // This is called on the main-thread only.
+ nsresult GetMimeTypeSync(nsACString& aMimeType);
+
+ // This is called on the main-thread only.
+ nsresult GetMimeTypeAsync();
+
+ Mutex mMutex;
+
+ // The access to this object is protected by mutex but is always nullified on
+ // the owning thread.
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+
+ // Touched only on the owning thread.
+ RefPtr<Promise> mPromise;
+
+ // Touched only on the owning thread.
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+
+ nsCOMPtr<nsIInputStream> mInputStream;
+ Maybe<IntRect> mCropRect;
+ Maybe<IntRect> mOriginalCropRect;
+ IntSize mSourceSize;
+
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+ void* mThread;
+};
+
+NS_IMPL_ISUPPORTS_INHERITED(CreateImageBitmapFromBlob, DiscardableRunnable,
+ imgIContainerCallback, nsIInputStreamCallback)
+
+class CreateImageBitmapFromBlobRunnable : public WorkerRunnable {
+ public:
+ explicit CreateImageBitmapFromBlobRunnable(WorkerPrivate* aWorkerPrivate,
+ CreateImageBitmapFromBlob* aTask,
+ layers::Image* aImage,
+ nsresult aStatus)
+ : WorkerRunnable(aWorkerPrivate),
+ mTask(aTask),
+ mImage(aImage),
+ mStatus(aStatus) {}
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ mTask->MimeTypeAndDecodeAndCropBlobCompletedOwningThread(mImage, mStatus);
+ return true;
+ }
+
+ private:
+ RefPtr<CreateImageBitmapFromBlob> mTask;
+ RefPtr<layers::Image> mImage;
+ nsresult mStatus;
+};
+
+static void AsyncCreateImageBitmapFromBlob(Promise* aPromise,
+ nsIGlobalObject* aGlobal,
+ Blob& aBlob,
+ const Maybe<IntRect>& aCropRect) {
+ // Let's identify the main-thread event target.
+ nsCOMPtr<nsIEventTarget> mainThreadEventTarget;
+ if (NS_IsMainThread()) {
+ mainThreadEventTarget = aGlobal->EventTargetFor(TaskCategory::Other);
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ mainThreadEventTarget = workerPrivate->MainThreadEventTarget();
+ }
+
+ RefPtr<CreateImageBitmapFromBlob> task = CreateImageBitmapFromBlob::Create(
+ aPromise, aGlobal, aBlob, aCropRect, mainThreadEventTarget);
+ if (NS_WARN_IF(!task)) {
+ aPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ NS_DispatchToCurrentThread(task);
+}
+
+/* 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()) {
+ if (aCropRect->Width() == 0) {
+ aRv.ThrowRangeError(
+ "The crop rect width passed to createImageBitmap must be nonzero");
+ return promise.forget();
+ }
+
+ if (aCropRect->Height() == 0) {
+ aRv.ThrowRangeError(
+ "The crop rect height passed to createImageBitmap must be nonzero");
+ 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.IsSVGImageElement()) {
+ MOZ_ASSERT(
+ NS_IsMainThread(),
+ "Creating ImageBitmap from SVGImageElement off the main thread.");
+ imageBitmap =
+ CreateInternal(aGlobal, aSrc.GetAsSVGImageElement(), 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 {
+ MOZ_CRASH("Unsupported type!");
+ return nullptr;
+ }
+
+ 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 alphaType_;
+ uint32_t writeOnly;
+
+ if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
+ !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
+ !JS_ReadUint32Pair(aReader, &alphaType_, &writeOnly)) {
+ 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_);
+ const auto alphaType = BitwiseCast<gfxAlphaType>(alphaType_);
+
+ // 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);
+ {
+#ifdef FUZZING
+ if (aIndex >= aClonedSurfaces.Length()) {
+ return nullptr;
+ }
+#endif
+ RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
+ RefPtr<ImageBitmap> imageBitmap =
+ new ImageBitmap(aParent, img, !!writeOnly, alphaType);
+
+ 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;
+ }
+
+ imageBitmap->mAllocatedImageData = true;
+ }
+
+ return &(value.toObject());
+}
+
+/*static*/
+bool ImageBitmap::WriteStructuredClone(
+ JSStructuredCloneWriter* aWriter,
+ nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
+ ImageBitmap* aImageBitmap) {
+ MOZ_ASSERT(aWriter);
+ MOZ_ASSERT(aImageBitmap);
+
+ if (!aImageBitmap->mData) {
+ // A closed image cannot be cloned.
+ return false;
+ }
+
+ 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 alphaType = BitwiseCast<uint32_t>(aImageBitmap->mAlphaType);
+
+ // 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, alphaType, aImageBitmap->mWriteOnly))) {
+ 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);
+ }
+ if (NS_WARN_IF(!dstDataSurface)) {
+ return false;
+ }
+ Factory::CopyDataSourceSurface(snapshot, dstDataSurface);
+ aClonedSurfaces.AppendElement(dstDataSurface);
+ return true;
+}
+
+size_t ImageBitmap::GetAllocatedSize() const {
+ if (!mAllocatedImageData) {
+ return 0;
+ }
+
+ // Calculate how many bytes are used.
+ if (mData->GetFormat() == mozilla::ImageFormat::PLANAR_YCBCR) {
+ return mData->AsPlanarYCbCrImage()->GetDataSize();
+ }
+
+ if (mData->GetFormat() == mozilla::ImageFormat::NV_IMAGE) {
+ return mData->AsNVImage()->GetBufferSize();
+ }
+
+ RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
+ const int bytesPerPixel = BytesPerPixel(surface->GetFormat());
+ return surface->GetSize().height * surface->GetSize().width * bytesPerPixel;
+}
+
+size_t BindingJSObjectMallocBytes(ImageBitmap* aBitmap) {
+ return aBitmap->GetAllocatedSize();
+}
+
+/* static */
+already_AddRefed<CreateImageBitmapFromBlob> CreateImageBitmapFromBlob::Create(
+ Promise* aPromise, nsIGlobalObject* aGlobal, Blob& aBlob,
+ const Maybe<IntRect>& aCropRect, nsIEventTarget* aMainThreadEventTarget) {
+ // Get the internal stream of the blob.
+ nsCOMPtr<nsIInputStream> stream;
+ ErrorResult error;
+ aBlob.Impl()->CreateInputStream(getter_AddRefs(stream), error);
+ if (NS_WARN_IF(error.Failed())) {
+ return nullptr;
+ }
+
+ if (!NS_InputStreamIsBuffered(stream)) {
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ stream.forget(), 4096);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ stream = bufferedStream;
+ }
+
+ RefPtr<CreateImageBitmapFromBlob> task = new CreateImageBitmapFromBlob(
+ aPromise, aGlobal, stream.forget(), aCropRect, aMainThreadEventTarget);
+
+ // Nothing to do for the main-thread.
+ if (NS_IsMainThread()) {
+ return task.forget();
+ }
+
+ // Let's use a WorkerRef to keep the worker alive if this is not the
+ // main-thread.
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+
+ RefPtr<StrongWorkerRef> workerRef =
+ StrongWorkerRef::Create(workerPrivate, "CreateImageBitmapFromBlob",
+ [task]() { task->WorkerShuttingDown(); });
+ if (NS_WARN_IF(!workerRef)) {
+ return nullptr;
+ }
+
+ task->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ return task.forget();
+}
+
+nsresult CreateImageBitmapFromBlob::StartMimeTypeAndDecodeAndCropBlob() {
+ MOZ_ASSERT(IsCurrentThread());
+
+ // Workers.
+ if (!NS_IsMainThread()) {
+ RefPtr<CreateImageBitmapFromBlob> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlob", [self]() {
+ nsresult rv = self->MimeTypeAndDecodeAndCropBlob();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ self->MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
+ }
+ });
+
+ return mMainThreadEventTarget->Dispatch(r.forget());
+ }
+
+ // Main-thread.
+ return MimeTypeAndDecodeAndCropBlob();
+}
+
+nsresult CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlob() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsAutoCString mimeType;
+ nsresult rv = GetMimeTypeSync(mimeType);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return GetMimeTypeAsync();
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return DecodeAndCropBlob(mimeType);
+}
+
+nsresult CreateImageBitmapFromBlob::DecodeAndCropBlob(
+ const nsACString& aMimeType) {
+ // Get the Component object.
+ nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
+ if (NS_WARN_IF(!imgtool)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Decode image.
+ nsresult rv = imgtool->DecodeImageAsync(mInputStream, aMimeType, this,
+ mMainThreadEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+static nsresult sniff_cb(nsIInputStream* aInputStream, void* aClosure,
+ const char* aFromRawSegment, uint32_t aToOffset,
+ uint32_t aCount, uint32_t* aWriteCount) {
+ nsACString* mimeType = static_cast<nsACString*>(aClosure);
+ MOZ_ASSERT(mimeType);
+
+ if (aCount > 0) {
+ imgLoader::GetMimeTypeFromContent(aFromRawSegment, aCount, *mimeType);
+ }
+
+ *aWriteCount = 0;
+
+ // We don't want to consume data from the stream.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult CreateImageBitmapFromBlob::GetMimeTypeSync(nsACString& aMimeType) {
+ uint32_t dummy;
+ return mInputStream->ReadSegments(sniff_cb, &aMimeType, 128, &dummy);
+}
+
+nsresult CreateImageBitmapFromBlob::GetMimeTypeAsync() {
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(mInputStream);
+ if (NS_WARN_IF(!asyncInputStream)) {
+ // If the stream is not async, why are we here?
+ return NS_ERROR_FAILURE;
+ }
+
+ return asyncInputStream->AsyncWait(this, 0, 128, mMainThreadEventTarget);
+}
+
+NS_IMETHODIMP
+CreateImageBitmapFromBlob::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ // The stream should have data now. Let's start from scratch again.
+ nsresult rv = MimeTypeAndDecodeAndCropBlob();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CreateImageBitmapFromBlob::OnImageReady(imgIContainer* aImgContainer,
+ nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_FAILED(aStatus)) {
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, aStatus);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aImgContainer);
+
+ // Get the surface out.
+ uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE |
+ imgIContainer::FLAG_ASYNC_NOTIFY |
+ imgIContainer::FLAG_WANT_DATA_SURFACE;
+ uint32_t whichFrame = imgIContainer::FRAME_FIRST;
+ RefPtr<SourceSurface> surface =
+ aImgContainer->GetFrame(whichFrame, frameFlags);
+
+ if (NS_WARN_IF(!surface)) {
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(
+ nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_OK;
+ }
+
+ // Store the sourceSize value for the
+ // MimeTypeAndDecodeAndCropBlobCompletedMainThread call.
+ mSourceSize = surface->GetSize();
+
+ // Crop the source surface if needed.
+ RefPtr<SourceSurface> croppedSurface = surface;
+
+ if (mCropRect.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, mCropRect.ref());
+ mCropRect->MoveTo(0, 0);
+ }
+
+ if (NS_WARN_IF(!croppedSurface)) {
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(
+ nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_OK;
+ }
+
+ // Create an Image from the source surface.
+ RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface);
+
+ if (NS_WARN_IF(!image)) {
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(
+ nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_OK;
+ }
+
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(image, NS_OK);
+ return NS_OK;
+}
+
+void CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlobCompletedMainThread(
+ layers::Image* aImage, nsresult aStatus) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsCurrentThread()) {
+ MutexAutoLock lock(mMutex);
+
+ if (!mWorkerRef) {
+ // The worker is already gone.
+ return;
+ }
+
+ RefPtr<CreateImageBitmapFromBlobRunnable> r =
+ new CreateImageBitmapFromBlobRunnable(mWorkerRef->Private(), this,
+ aImage, aStatus);
+ r->Dispatch();
+ return;
+ }
+
+ MimeTypeAndDecodeAndCropBlobCompletedOwningThread(aImage, aStatus);
+}
+
+void CreateImageBitmapFromBlob::
+ MimeTypeAndDecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
+ nsresult aStatus) {
+ MOZ_ASSERT(IsCurrentThread());
+
+ if (!mPromise) {
+ // The worker is going to be released soon. No needs to continue.
+ return;
+ }
+
+ // Let's release what has to be released on the owning thread.
+ auto raii = MakeScopeExit([&] {
+ // Doing this we also release the worker.
+ mWorkerRef = nullptr;
+
+ mPromise = nullptr;
+ mGlobalObject = nullptr;
+ });
+
+ if (NS_WARN_IF(NS_FAILED(aStatus))) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ // Create ImageBitmap object.
+ RefPtr<ImageBitmap> imageBitmap =
+ new ImageBitmap(mGlobalObject, aImage, false /* write-only */);
+
+ if (mCropRect.isSome()) {
+ ErrorResult rv;
+ imageBitmap->SetPictureRect(mCropRect.ref(), rv);
+
+ if (rv.Failed()) {
+ mPromise->MaybeReject(std::move(rv));
+ return;
+ }
+ }
+
+ imageBitmap->mAllocatedImageData = true;
+
+ mPromise->MaybeResolve(imageBitmap);
+}
+
+void CreateImageBitmapFromBlob::WorkerShuttingDown() {
+ MOZ_ASSERT(IsCurrentThread());
+
+ MutexAutoLock lock(mMutex);
+
+ // Let's release all the non-thread-safe objects now.
+ mWorkerRef = nullptr;
+ mPromise = nullptr;
+ mGlobalObject = nullptr;
+}
+
+} // namespace mozilla::dom