diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/canvas/ImageBitmap.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/ImageBitmap.cpp')
-rw-r--r-- | dom/canvas/ImageBitmap.cpp | 2228 |
1 files changed, 2228 insertions, 0 deletions
diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp new file mode 100644 index 0000000000..0626e065b4 --- /dev/null +++ b/dom/canvas/ImageBitmap.cpp @@ -0,0 +1,2228 @@ +/* -*- 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/Scale.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 "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(); +} + +/* + * This helper function scales 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_. + * + */ +static already_AddRefed<DataSourceSurface> ScaleDataSourceSurface( + DataSourceSurface* aSurface, const ImageBitmapOptions& aOptions) { + MOZ_ASSERT(aSurface); + + const SurfaceFormat format = aSurface->GetFormat(); + const int bytesPerPixel = BytesPerPixel(format); + + const IntSize srcSize = aSurface->GetSize(); + int32_t tmp; + + CheckedInt<int32_t> checked; + CheckedInt<int32_t> dstWidth( + aOptions.mResizeWidth.WasPassed() ? aOptions.mResizeWidth.Value() : 0); + CheckedInt<int32_t> dstHeight( + aOptions.mResizeHeight.WasPassed() ? aOptions.mResizeHeight.Value() : 0); + + if (!dstWidth.isValid() || !dstHeight.isValid()) { + return nullptr; + } + + if (!dstWidth.value()) { + checked = srcSize.width * dstHeight; + if (!checked.isValid()) { + return nullptr; + } + + tmp = ceil(checked.value() / double(srcSize.height)); + dstWidth = tmp; + } else if (!dstHeight.value()) { + checked = srcSize.height * dstWidth; + if (!checked.isValid()) { + return nullptr; + } + + tmp = ceil(checked.value() / double(srcSize.width)); + dstHeight = tmp; + } + + const IntSize dstSize(dstWidth.value(), dstHeight.value()); + const int32_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; + } + + // 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(); + uint8_t* dstBufferPtr = dstMap.GetData(); + + bool res = Scale(srcBufferPtr, srcSize.width, srcSize.height, + srcMap.GetStride(), dstBufferPtr, dstSize.width, + dstSize.height, dstMap.GetStride(), aSurface->GetFormat()); + if (!res) { + return nullptr; + } + + return dstDataSurface.forget(); +} + +static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) { + MOZ_ASSERT(aSurface); + + // Invert in y direction. + DataSourceSurface::ScopedMap srcMap(aSurface, DataSourceSurface::READ_WRITE); + if (NS_WARN_IF(!srcMap.IsMapped())) { + return nullptr; + } + + const IntSize srcSize = aSurface->GetSize(); + uint8_t* srcBufferPtr = srcMap.GetData(); + const uint32_t stride = srcMap.GetStride(); + + CheckedInt<uint32_t> copiedBytesPerRaw = CheckedInt<uint32_t>(stride); + if (!copiedBytesPerRaw.isValid()) { + return nullptr; + } + + for (int i = 0; i < srcSize.height / 2; ++i) { + std::swap_ranges(srcBufferPtr + stride * i, srcBufferPtr + stride * (i + 1), + srcBufferPtr + stride * (srcSize.height - 1 - i)); + } + + return aSurface; +} + +static DataSourceSurface* AlphaPremultiplyDataSourceSurface( + DataSourceSurface* aSurface, const bool forward = true) { + MOZ_ASSERT(aSurface); + + DataSourceSurface::MappedSurface surfaceMap; + + if (aSurface->Map(DataSourceSurface::MapType::READ_WRITE, &surfaceMap)) { + if (forward) { + PremultiplyData(surfaceMap.mData, surfaceMap.mStride, + aSurface->GetFormat(), surfaceMap.mData, + surfaceMap.mStride, aSurface->GetFormat(), + aSurface->GetSize()); + } else { + UnpremultiplyData(surfaceMap.mData, surfaceMap.mStride, + aSurface->GetFormat(), surfaceMap.mData, + surfaceMap.mStride, aSurface->GetFormat(), + aSurface->GetSize()); + } + + aSurface->Unmap(); + } else { + return nullptr; + } + + return aSurface; +} + +/* + * 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, + const ImageBitmapOptions& aOptions) { + 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; + } + + if (aOptions.mImageOrientation == ImageOrientation::FlipY) { + result = FlipYDataSourceSurface(result); + + if (NS_WARN_IF(!result)) { + return nullptr; + } + } + + if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) { + result = AlphaPremultiplyDataSourceSurface(result); + + if (NS_WARN_IF(!result)) { + return nullptr; + } + } + + if (aOptions.mResizeWidth.WasPassed() || aOptions.mResizeHeight.WasPassed()) { + dataSurface = result->GetDataSurface(); + result = ScaleDataSourceSurface(dataSurface, aOptions); + 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, + const ImageBitmapOptions& aOptions) { + MOZ_ASSERT(NS_IsMainThread()); + + // Copy and crop the source buffer into a SourceSurface. + RefPtr<SourceSurface> rgbaSurface = CreateSurfaceFromRawData( + aSize, aStride, aFormat, aBuffer, aBufferLength, aCropRect, aOptions); + + 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, + const ImageBitmapOptions& aOptions) + : WorkerMainThreadRunnable( + GetCurrentThreadWorkerPrivate(), + "ImageBitmap :: Create Image from Raw Data"_ns), + mImage(aImage), + mBuffer(aBuffer), + mBufferLength(aBufferLength), + mStride(aStride), + mFormat(aFormat), + mSize(aSize), + mCropRect(aCropRect), + mOptions(aOptions) { + 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, mOptions); + + 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; + const ImageBitmapOptions mOptions; +}; + +/* + * 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, + const ImageBitmapOptions& aOptions, gfxAlphaType* aAlphaType, + ErrorResult& aRv) { + uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | + nsLayoutUtils::SFE_ORIENTATION_FROM_IMAGE; + + // by default surfaces have premultiplied alpha + // attempt to get non premultiplied if required + if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { + flags |= nsLayoutUtils::SFE_ALLOW_NON_PREMULT; + } + + if (aOptions.mColorSpaceConversion == ColorSpaceConversion::None && + aElement.IsHTMLElement(nsGkAtoms::img)) { + flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION; + } + + Maybe<int32_t> resizeWidth, resizeHeight; + if (aOptions.mResizeWidth.WasPassed()) { + if (!CheckedInt32(aOptions.mResizeWidth.Value()).isValid()) { + aRv.ThrowInvalidStateError("resizeWidth is too large"); + return nullptr; + } + resizeWidth.emplace(aOptions.mResizeWidth.Value()); + } + if (aOptions.mResizeHeight.WasPassed()) { + if (!CheckedInt32(aOptions.mResizeHeight.Value()).isValid()) { + aRv.ThrowInvalidStateError("resizeHeight is too large"); + return nullptr; + } + resizeHeight.emplace(aOptions.mResizeHeight.Value()); + } + SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromElement( + &aElement, resizeWidth, resizeHeight, flags); + + RefPtr<SourceSurface> surface = res.GetSourceSurface(); + if (NS_WARN_IF(!surface)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + *aWriteOnly = res.mIsWriteOnly; + *aAlphaType = res.mAlphaType; + + return surface.forget(); +} + +ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData, + bool aWriteOnly, gfxAlphaType aAlphaType) + : mParent(aGlobal), + mData(aData), + mSurface(nullptr), + 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; + mPictureRect.SetEmpty(); +} + +void ImageBitmap::OnShutdown() { + mShutdownObserver = nullptr; + + Close(); +} + +void ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv) { + mPictureRect = FixUpNegativeDimension(aRect, aRv); + mSurface = nullptr; +} + +/* + * 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; + } + + // We may have a cached surface optimized for the last DrawTarget. If it was + // created via DrawTargetRecording, it may not be suitable for use with the + // current DrawTarget, as we can only do readbacks via the + // PersistentBufferProvider for the canvas, and not for individual + // SourceSurfaceRecording objects. In such situations, the only thing we can + // do is clear our cache and extract a new SourceSurface from mData. + if (mSurface && mSurface->GetType() == gfx::SurfaceType::RECORDING && + !aTarget->IsRecording()) { + RefPtr<gfx::DataSourceSurface> dataSurface = mSurface->GetDataSurface(); + if (!dataSurface) { + mSurface = nullptr; + } + } + + // If we have a surface, then it is already cropped and premultiplied. + if (mSurface) { + return do_AddRef(mSurface); + } + + RefPtr<gfx::SourceSurface> surface = mData->GetAsSourceSurface(); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + IntRect surfRect(0, 0, surface->GetSize().width, surface->GetSize().height); + SurfaceFormat format = surface->GetFormat(); + bool isOpaque = IsOpaque(format); + bool mustPremultiply = mAlphaType == gfxAlphaType::NonPremult && !isOpaque; + bool hasCopied = false; + + // 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()) { + return nullptr; + } + + 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. Ensure that the + // format matches the surface, even though the DT type is similar to the + // destination, i.e. blending an alpha surface to an opaque DT. However, + // any pixels outside the surface portion must be filled with transparent + // black, even if the surface is opaque, so force to an alpha format in + // that case. + if (!surfPortion.IsEqualEdges(mPictureRect) && isOpaque) { + format = SurfaceFormat::B8G8R8A8; + } + + // If we need to pre-multiply the alpha, then we need to be able to + // read/write the pixel data, and as such, we want to avoid creating a + // SourceSurfaceRecording for the same reasons earlier in this method. + RefPtr<DrawTarget> cropped; + if (mustPremultiply && aTarget->IsRecording()) { + cropped = Factory::CreateDrawTarget(BackendType::SKIA, + mPictureRect.Size(), format); + } else { + cropped = aTarget->CreateSimilarDrawTarget(mPictureRect.Size(), format); + } + + if (NS_WARN_IF(!cropped)) { + return nullptr; + } + + cropped->CopySurface(surface, surfPortion, dest); + surface = cropped->GetBackingSurface(); + hasCopied = true; + if (NS_WARN_IF(!surface)) { + return nullptr; + } + } + + // 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 the + // crop rect imputed transparency, and the original surface was opaque, we + // can skip doing the pre-multiply here as the only transparent pixels are + // already transparent black. + if (mustPremultiply) { + MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::R8G8B8A8 || + surface->GetFormat() == SurfaceFormat::B8G8R8A8 || + surface->GetFormat() == SurfaceFormat::A8R8G8B8); + + RefPtr<DataSourceSurface> srcSurface = surface->GetDataSurface(); + if (NS_WARN_IF(!srcSurface)) { + return nullptr; + } + + if (hasCopied) { + // If we are using our own local copy, then we can safely premultiply in + // place without an additional allocation. + DataSourceSurface::ScopedMap map(srcSurface, + DataSourceSurface::READ_WRITE); + if (!map.IsMapped()) { + gfxCriticalError() << "Failed to map surface for premultiplying alpha."; + return nullptr; + } + + PremultiplyData(map.GetData(), map.GetStride(), srcSurface->GetFormat(), + map.GetData(), map.GetStride(), srcSurface->GetFormat(), + surface->GetSize()); + } else { + RefPtr<DataSourceSurface> dstSurface = Factory::CreateDataSourceSurface( + srcSurface->GetSize(), srcSurface->GetFormat()); + if (NS_WARN_IF(!dstSurface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap srcMap(srcSurface, DataSourceSurface::READ); + if (!srcMap.IsMapped()) { + gfxCriticalError() + << "Failed to map source surface for premultiplying alpha."; + return nullptr; + } + + DataSourceSurface::ScopedMap dstMap(dstSurface, DataSourceSurface::WRITE); + if (!dstMap.IsMapped()) { + gfxCriticalError() + << "Failed to map destination surface for premultiplying alpha."; + return nullptr; + } + + PremultiplyData(srcMap.GetData(), srcMap.GetStride(), + srcSurface->GetFormat(), dstMap.GetData(), + dstMap.GetStride(), dstSurface->GetFormat(), + dstSurface->GetSize()); + + surface = std::move(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 = aTarget->OptimizeSourceSurface(surface); + if (!mSurface) { + mSurface = std::move(surface); + } + return do_AddRef(mSurface); +} + +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::CreateImageBitmapInternal( + nsIGlobalObject* aGlobal, gfx::SourceSurface* aSurface, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + const bool aWriteOnly, const bool aAllocatedImageData, const bool aMustCopy, + const gfxAlphaType aAlphaType, ErrorResult& aRv) { + bool needToReportMemoryAllocation = aAllocatedImageData; + const IntSize srcSize = aSurface->GetSize(); + IntRect cropRect = + aCropRect.valueOr(IntRect(0, 0, srcSize.width, srcSize.height)); + + RefPtr<SourceSurface> surface = aSurface; + RefPtr<DataSourceSurface> dataSurface; + + // handle alpha premultiplication if surface not of correct type + + gfxAlphaType alphaType = aAlphaType; + bool mustCopy = aMustCopy; + bool requiresPremultiply = false; + bool requiresUnpremultiply = false; + + if (!IsOpaque(surface->GetFormat())) { + if (aAlphaType == gfxAlphaType::Premult && + aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { + requiresUnpremultiply = true; + alphaType = gfxAlphaType::NonPremult; + if (!aAllocatedImageData) { + mustCopy = true; + } + } else if (aAlphaType == gfxAlphaType::NonPremult && + aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) { + requiresPremultiply = true; + alphaType = gfxAlphaType::Premult; + if (!aAllocatedImageData) { + mustCopy = true; + } + } + } + + /* + * if we don't own the data and need to create a new buffer to flip Y. + * or + * we need to crop and flip, where crop must come first. + * or + * Or the caller demands a copy (WebGL contexts). + */ + if ((aOptions.mImageOrientation == ImageOrientation::FlipY && + (!aAllocatedImageData || aCropRect.isSome())) || + mustCopy) { + dataSurface = surface->GetDataSurface(); + + dataSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect); + if (NS_WARN_IF(!dataSurface)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + surface = dataSurface; + cropRect.SetRect(0, 0, dataSurface->GetSize().width, + dataSurface->GetSize().height); + needToReportMemoryAllocation = true; + } + + // flip image in Y direction + if (aOptions.mImageOrientation == ImageOrientation::FlipY) { + if (!dataSurface) { + dataSurface = surface->GetDataSurface(); + } + + surface = FlipYDataSourceSurface(dataSurface); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + } + + if (requiresPremultiply) { + if (!dataSurface) { + dataSurface = surface->GetDataSurface(); + } + + surface = AlphaPremultiplyDataSourceSurface(dataSurface, true); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + } + + // resize if required + if (aOptions.mResizeWidth.WasPassed() || aOptions.mResizeHeight.WasPassed()) { + if (!dataSurface) { + dataSurface = surface->GetDataSurface(); + }; + + surface = ScaleDataSourceSurface(dataSurface, aOptions); + if (NS_WARN_IF(!surface)) { + aRv.ThrowInvalidStateError("Failed to create resized image"); + return nullptr; + } + + needToReportMemoryAllocation = true; + cropRect.SetRect(0, 0, surface->GetSize().width, surface->GetSize().height); + } + + if (requiresUnpremultiply) { + if (!dataSurface) { + dataSurface = surface->GetDataSurface(); + } + + surface = AlphaPremultiplyDataSourceSurface(dataSurface, false); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + } + + // Create an Image from the SourceSurface. + RefPtr<layers::Image> data = CreateImageFromSurface(surface); + RefPtr<ImageBitmap> ret = + new ImageBitmap(aGlobal, data, aWriteOnly, alphaType); + + if (needToReportMemoryAllocation) { + ret->mAllocatedImageData = true; + } + + // Set the picture rectangle. + ret->SetPictureRect(cropRect, aRv); + + return ret.forget(); +} + +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + 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; + gfxAlphaType alphaType = gfxAlphaType::NonPremult; + + // Get the SourceSurface out from the image element and then do security + // checking. + RefPtr<SourceSurface> surface = GetSurfaceFromElement( + aGlobal, aImageEl, &writeOnly, aOptions, &alphaType, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + bool needToReportMemoryAllocation = false; + return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, + writeOnly, needToReportMemoryAllocation, + false, alphaType, aRv); +} +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, SVGImageElement& aImageEl, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + ErrorResult& aRv) { + bool writeOnly = true; + gfxAlphaType alphaType = gfxAlphaType::NonPremult; + + // Get the SourceSurface out from the image element and then do security + // checking. + RefPtr<SourceSurface> surface = GetSurfaceFromElement( + aGlobal, aImageEl, &writeOnly, aOptions, &alphaType, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + bool needToReportMemoryAllocation = false; + + return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, + writeOnly, needToReportMemoryAllocation, + false, alphaType, aRv); +} + +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + ErrorResult& aRv) { + aVideoEl.LogVisibility( + 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<SourceSurface> surface = data->GetAsSourceSurface(); + if (!surface) { + // preserve original behavior in case of unavailble surface + RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly); + return ret.forget(); + } + + bool needToReportMemoryAllocation = false; + + return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, + writeOnly, needToReportMemoryAllocation, + false, gfxAlphaType::Premult, aRv); +} + +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + ErrorResult& aRv) { + if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + bool writeOnly = true; + gfxAlphaType alphaType = gfxAlphaType::NonPremult; + + RefPtr<SourceSurface> surface = GetSurfaceFromElement( + aGlobal, aCanvasEl, &writeOnly, aOptions, &alphaType, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (!writeOnly) { + writeOnly = aCanvasEl.IsWriteOnly(); + } + + // 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; + bool mustCopy = false; + + if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 || + aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2 || + aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGPU) && + aCropRect.isSome()) { + mustCopy = true; + } + + return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, + writeOnly, needToReportMemoryAllocation, + mustCopy, alphaType, aRv); +} + +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, OffscreenCanvas& aOffscreenCanvas, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + ErrorResult& aRv) { + if (aOffscreenCanvas.Width() == 0) { + aRv.ThrowInvalidStateError("Passed-in canvas has width 0"); + return nullptr; + } + + if (aOffscreenCanvas.Height() == 0) { + aRv.ThrowInvalidStateError("Passed-in canvas has height 0"); + return nullptr; + } + + uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE; + + // by default surfaces have premultiplied alpha + // attempt to get non premultiplied if required + if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { + flags |= nsLayoutUtils::SFE_ALLOW_NON_PREMULT; + } + + SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas, flags); + + RefPtr<SourceSurface> surface = res.GetSourceSurface(); + if (NS_WARN_IF(!surface)) { + aRv.ThrowInvalidStateError("Passed-in canvas failed to create snapshot"); + return nullptr; + } + + gfxAlphaType alphaType = res.mAlphaType; + bool writeOnly = res.mIsWriteOnly; + + // If the OffscreenCanvas's rendering context is WebGL/WebGPU, then the + // snapshot we got from the OffscreenCanvas is a DataSourceSurface which + // is a copy of the rendering context. We handle cropping in this case. + bool needToReportMemoryAllocation = false; + bool mustCopy = + aCropRect.isSome() && + (aOffscreenCanvas.GetContextType() == CanvasContextType::WebGL1 || + aOffscreenCanvas.GetContextType() == CanvasContextType::WebGL2 || + aOffscreenCanvas.GetContextType() == CanvasContextType::WebGPU); + + return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, + writeOnly, needToReportMemoryAllocation, + mustCopy, alphaType, aRv); +} + +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, ImageData& aImageData, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + ErrorResult& aRv) { + // Copy data into SourceSurface. + RootedSpiderMonkeyInterface<Uint8ClampedArray> array(RootingCx()); + if (!array.Init(aImageData.GetDataObject())) { + aRv.ThrowInvalidStateError( + "Failed to extract Uint8ClampedArray from ImageData (security check " + "failed?)"); + return nullptr; + } + array.ComputeState(); + const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8; + // ImageData's underlying data is not alpha-premultiplied. + auto alphaType = (aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) + ? gfxAlphaType::Premult + : 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) { + aRv.ThrowInvalidStateError("Passed-in image is empty"); + return nullptr; + } + + if ((imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) { + aRv.ThrowInvalidStateError("Data size / image format mismatch"); + 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, aOptions); + } else { + RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task = + new CreateImageFromRawDataInMainThreadSyncTask( + fixedData, dataLength, imageStride, FORMAT, imageSize, aCropRect, + getter_AddRefs(data), aOptions); + task->Dispatch(Canceling, aRv); + } + + if (NS_WARN_IF(!data)) { + aRv.ThrowInvalidStateError("Failed to create internal image"); + 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, const ImageBitmapOptions& aOptions, + 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; + } + + bool needToReportMemoryAllocation = true; + + return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions, + writeOnly, needToReportMemoryAllocation, + false, gfxAlphaType::Premult, aRv); +} + +/* static */ +already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal( + nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap, + const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions, + ErrorResult& aRv) { + if (!aImageBitmap.mData) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + IntRect cropRect; + RefPtr<SourceSurface> surface; + RefPtr<DataSourceSurface> dataSurface; + gfxAlphaType alphaType; + + bool needToReportMemoryAllocation = false; + + if (aImageBitmap.mSurface && + (dataSurface = aImageBitmap.mSurface->GetDataSurface())) { + // the source imageBitmap already has a cropped surface, and we can get a + // DataSourceSurface from it, so just use it directly + surface = aImageBitmap.mSurface; + cropRect = aCropRect.valueOr(IntRect(IntPoint(0, 0), surface->GetSize())); + alphaType = IsOpaque(surface->GetFormat()) ? gfxAlphaType::Opaque + : gfxAlphaType::Premult; + } else { + RefPtr<layers::Image> data = aImageBitmap.mData; + surface = data->GetAsSourceSurface(); + if (NS_WARN_IF(!surface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + cropRect = aImageBitmap.mPictureRect; + alphaType = aImageBitmap.mAlphaType; + if (aCropRect.isSome()) { + // get new crop rect relative to original uncropped surface + IntRect newCropRect = aCropRect.ref(); + newCropRect = FixUpNegativeDimension(newCropRect, aRv); + + newCropRect.MoveBy(cropRect.X(), cropRect.Y()); + + if (cropRect.Contains(newCropRect)) { + // new crop region within existing surface + // safe to just crop this with new rect + cropRect = newCropRect; + } else { + // crop includes area outside original cropped region + // create new surface cropped by original bitmap crop rect + RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); + + surface = CropAndCopyDataSourceSurface(dataSurface, cropRect); + if (NS_WARN_IF(!surface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + needToReportMemoryAllocation = true; + cropRect = aCropRect.ref(); + } + } + } + + return CreateImageBitmapInternal( + aGlobal, surface, Some(cropRect), aOptions, aImageBitmap.mWriteOnly, + needToReportMemoryAllocation, false, alphaType, aRv); +} + +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, + const ImageBitmapOptions& aOptions); + + 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, + const ImageBitmapOptions& aOptions) + : DiscardableRunnable("dom::CreateImageBitmapFromBlob"), + + mMutex("dom::CreateImageBitmapFromBlob::mMutex"), + mPromise(aPromise), + mGlobalObject(aGlobal), + mInputStream(std::move(aInputStream)), + mCropRect(aCropRect), + mMainThreadEventTarget(aMainThreadEventTarget), + mOptions(aOptions), + 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 MOZ_UNANNOTATED; + + // 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; + nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; + const ImageBitmapOptions mOptions; + 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, + const ImageBitmapOptions& aOptions) { + // 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, aOptions); + 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, const ImageBitmapOptions& aOptions, + 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(); + } + } + + if (aOptions.mResizeWidth.WasPassed() && aOptions.mResizeWidth.Value() == 0) { + aRv.ThrowInvalidStateError( + "The resizeWidth passed to createImageBitmap must be nonzero"); + return promise.forget(); + } + + if (aOptions.mResizeHeight.WasPassed() && + aOptions.mResizeHeight.Value() == 0) { + aRv.ThrowInvalidStateError( + "The resizeHeight 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, aOptions, aRv); + } else if (aSrc.IsSVGImageElement()) { + MOZ_ASSERT( + NS_IsMainThread(), + "Creating ImageBitmap from SVGImageElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsSVGImageElement(), + aCropRect, aOptions, aRv); + } else if (aSrc.IsHTMLVideoElement()) { + MOZ_ASSERT( + NS_IsMainThread(), + "Creating ImageBitmap from HTMLVideoElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(), + aCropRect, aOptions, aRv); + } else if (aSrc.IsHTMLCanvasElement()) { + MOZ_ASSERT( + NS_IsMainThread(), + "Creating ImageBitmap from HTMLCanvasElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(), + aCropRect, aOptions, aRv); + } else if (aSrc.IsOffscreenCanvas()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsOffscreenCanvas(), + aCropRect, aOptions, aRv); + } else if (aSrc.IsImageData()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect, + aOptions, aRv); + } else if (aSrc.IsCanvasRenderingContext2D()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from CanvasRenderingContext2D off the " + "main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(), + aCropRect, aOptions, aRv); + } else if (aSrc.IsImageBitmap()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect, + aOptions, aRv); + } else if (aSrc.IsBlob()) { + AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(), + aCropRect, aOptions); + 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*/ +void ImageBitmap::WriteStructuredClone( + JSStructuredCloneWriter* aWriter, + nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces, + ImageBitmap* aImageBitmap, ErrorResult& aRv) { + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aImageBitmap); + + if (aImageBitmap->IsWriteOnly()) { + return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, is write-only"); + } + + if (!aImageBitmap->mData) { + // A closed image cannot be cloned. + return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, is closed"); + } + + 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 aRv.ThrowDataCloneError( + "Cannot clone ImageBitmap, failed to write params"); + } + + RefPtr<SourceSurface> surface = aImageBitmap->mData->GetAsSourceSurface(); + if (NS_WARN_IF(!surface)) { + return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, no surface"); + } + + RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface(); + if (NS_WARN_IF(!snapshot)) { + return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, no data surface"); + } + + 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); + if (NS_WARN_IF(!map.IsMapped())) { + return aRv.ThrowDataCloneError( + "Cannot clone ImageBitmap, cannot map surface"); + } + + dstDataSurface = Factory::CreateDataSourceSurfaceWithStride( + snapshot->GetSize(), snapshot->GetFormat(), map.GetStride(), true); + } + if (NS_WARN_IF(!dstDataSurface)) { + return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, out of memory"); + } + Factory::CopyDataSourceSurface(snapshot, dstDataSurface); + aClonedSurfaces.AppendElement(dstDataSurface); +} + +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(); + if (NS_WARN_IF(!surface)) { + return 0; + } + + 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, + const ImageBitmapOptions& aOptions) { + // 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, + aOptions); + + // 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; + uint32_t whichFrame = imgIContainer::FRAME_FIRST; + + if (mOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { + frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA; + } + + if (mOptions.mColorSpaceConversion == ColorSpaceConversion::None) { + frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION; + } + + RefPtr<SourceSurface> surface = + aImgContainer->GetFrame(whichFrame, frameFlags); + + if (NS_WARN_IF(!surface)) { + MimeTypeAndDecodeAndCropBlobCompletedMainThread( + nullptr, NS_ERROR_DOM_INVALID_STATE_ERR); + return NS_OK; + } + + // Crop the source surface if needed. + RefPtr<SourceSurface> croppedSurface = surface; + RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface(); + + // force a copy into unprotected memory as a side effect of + // CropAndCopyDataSourceSurface + bool copyRequired = mCropRect.isSome() || + mOptions.mImageOrientation == ImageOrientation::FlipY; + + if (copyRequired) { + // 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. + + IntRect cropRect = + mCropRect.isSome() ? mCropRect.ref() : dataSurface->GetRect(); + + croppedSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect); + if (NS_WARN_IF(!croppedSurface)) { + MimeTypeAndDecodeAndCropBlobCompletedMainThread( + nullptr, NS_ERROR_DOM_INVALID_STATE_ERR); + return NS_OK; + } + + dataSurface = croppedSurface->GetDataSurface(); + + if (mCropRect.isSome()) { + mCropRect->SetRect(0, 0, dataSurface->GetSize().width, + dataSurface->GetSize().height); + } + } + + if (mOptions.mImageOrientation == ImageOrientation::FlipY) { + croppedSurface = FlipYDataSourceSurface(dataSurface); + } + + if (mOptions.mResizeWidth.WasPassed() || mOptions.mResizeHeight.WasPassed()) { + dataSurface = croppedSurface->GetDataSurface(); + croppedSurface = ScaleDataSourceSurface(dataSurface, mOptions); + if (NS_WARN_IF(!croppedSurface)) { + MimeTypeAndDecodeAndCropBlobCompletedMainThread( + nullptr, NS_ERROR_DOM_INVALID_STATE_ERR); + return NS_OK; + } + if (mCropRect.isSome()) { + mCropRect->SetRect(0, 0, croppedSurface->GetSize().width, + croppedSurface->GetSize().height); + } + } + + 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; + } + + gfxAlphaType alphaType = gfxAlphaType::Premult; + + if (mOptions.mPremultiplyAlpha == PremultiplyAlpha::None) { + alphaType = gfxAlphaType::NonPremult; + } + + // Create ImageBitmap object. + RefPtr<ImageBitmap> imageBitmap = + new ImageBitmap(mGlobalObject, aImage, false /* write-only */, alphaType); + + 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 |