--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -395,16 +395,17 @@ HasRasterImage(HTMLImageElement& aImageE
ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
bool aIsPremultipliedAlpha /* = true */)
: mParent(aGlobal)
, mData(aData)
, mSurface(nullptr)
, mDataWrapper(new ImageUtils(mData))
, mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height)
, mIsPremultipliedAlpha(aIsPremultipliedAlpha)
+ , mIsCroppingAreaOutSideOfSourceImage(false)
{
MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
}
ImageBitmap::~ImageBitmap()
{
}
@@ -423,16 +424,33 @@ ImageBitmap::Close()
}
void
ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv)
{
mPictureRect = FixUpNegativeDimension(aRect, aRv);
}
+void
+ImageBitmap::SetIsCroppingAreaOutSideOfSourceImage(const IntSize& aSourceSize,
+ const Maybe<IntRect>& aCroppingRect)
+{
+ // No cropping at all.
+ if (aCroppingRect.isNothing()) {
+ mIsCroppingAreaOutSideOfSourceImage = false;
+ return;
+ }
+
+ if (aCroppingRect->X() < 0 || aCroppingRect->Y() < 0 ||
+ aCroppingRect->Width() > aSourceSize.width ||
+ aCroppingRect->Height() > aSourceSize.height) {
+ mIsCroppingAreaOutSideOfSourceImage = true;
+ }
+}
+
static already_AddRefed<SourceSurface>
ConvertColorFormatIfNeeded(RefPtr<SourceSurface> aSurface)
{
const SurfaceFormat srcFormat = aSurface->GetFormat();
if (srcFormat == SurfaceFormat::R8G8B8A8 ||
srcFormat == SurfaceFormat::B8G8R8A8 ||
srcFormat == SurfaceFormat::R8G8B8X8 ||
srcFormat == SurfaceFormat::B8G8R8X8 ||
@@ -666,32 +684,36 @@ ImageBitmap::TransferAsImage()
}
ImageBitmapCloneData*
ImageBitmap::ToCloneData()
{
ImageBitmapCloneData* result = new ImageBitmapCloneData();
result->mPictureRect = mPictureRect;
result->mIsPremultipliedAlpha = mIsPremultipliedAlpha;
+ result->mIsCroppingAreaOutSideOfSourceImage = mIsCroppingAreaOutSideOfSourceImage;
RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
result->mSurface = surface->GetDataSurface();
MOZ_ASSERT(result->mSurface);
return result;
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateFromCloneData(nsIGlobalObject* aGlobal,
ImageBitmapCloneData* aData)
{
- RefPtr<layers::Image> data =
- CreateImageFromSurface(aData->mSurface);
+ RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface);
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data,
aData->mIsPremultipliedAlpha);
+
+ ret->mIsCroppingAreaOutSideOfSourceImage =
+ aData->mIsCroppingAreaOutSideOfSourceImage;
+
ErrorResult rv;
ret->SetPictureRect(aData->mPictureRect, rv);
return ret.forget();
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateFromOffscreenCanvas(nsIGlobalObject* aGlobal,
OffscreenCanvas& aOffscreenCanvas,
@@ -755,16 +777,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
// Set the picture rectangle.
if (ret && aCropRect.isSome()) {
ret->SetPictureRect(aCropRect.ref(), aRv);
}
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
+
return ret.forget();
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
// Check network state.
@@ -800,16 +825,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
layers::Image* data = lockImage.GetImage();
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
// Set the picture rectangle.
if (ret && aCropRect.isSome()) {
ret->SetPictureRect(aCropRect.ref(), aRv);
}
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(data->GetSize(), aCropRect);
+
return ret.forget();
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) {
@@ -860,16 +888,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
// Set the picture rectangle.
if (ret && aCropRect.isSome()) {
ret->SetPictureRect(cropRect, aRv);
}
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
+
return ret.forget();
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
// Copy data into SourceSurface.
@@ -918,16 +949,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
// Create an ImageBimtap.
// ImageData's underlying data is not alpha-premultiplied.
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, false);
// The cropping information has been handled in the CreateImageFromRawData()
// function.
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(imageSize, aCropRect);
+
return ret.forget();
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
// Check origin-clean.
@@ -958,16 +992,19 @@ ImageBitmap::CreateInternal(nsIGlobalObj
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data);
// Set the picture rectangle.
if (ret && aCropRect.isSome()) {
ret->SetPictureRect(aCropRect.ref(), aRv);
}
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ ret->SetIsCroppingAreaOutSideOfSourceImage(surface->GetSize(), aCropRect);
+
return ret.forget();
}
/* static */ already_AddRefed<ImageBitmap>
ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
if (!aImageBitmap.mData) {
@@ -978,16 +1015,24 @@ ImageBitmap::CreateInternal(nsIGlobalObj
RefPtr<layers::Image> data = aImageBitmap.mData;
RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, aImageBitmap.mIsPremultipliedAlpha);
// Set the picture rectangle.
if (ret && aCropRect.isSome()) {
ret->SetPictureRect(aCropRect.ref(), aRv);
}
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ if (aImageBitmap.mIsCroppingAreaOutSideOfSourceImage == true) {
+ ret->mIsCroppingAreaOutSideOfSourceImage = true;
+ } else {
+ ret->SetIsCroppingAreaOutSideOfSourceImage(aImageBitmap.mPictureRect.Size(),
+ aCropRect);
+ }
+
return ret.forget();
}
class FulfillImageBitmapPromise
{
protected:
FulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
: mPromise(aPromise)
@@ -1091,25 +1136,29 @@ DecodeBlob(Blob& aBlob)
if (NS_WARN_IF(!surface)) {
return nullptr;
}
return surface.forget();
}
static already_AddRefed<layers::Image>
-DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect)
+DecodeAndCropBlob(Blob& aBlob, Maybe<IntRect>& aCropRect,
+ /*Output*/ IntSize& sourceSize)
{
// Decode the blob into a SourceSurface.
RefPtr<SourceSurface> surface = DecodeBlob(aBlob);
if (NS_WARN_IF(!surface)) {
return nullptr;
}
+ // Set the _sourceSize_ output parameter.
+ sourceSize = surface->GetSize();
+
// Crop the source surface if needed.
RefPtr<SourceSurface> croppedSurface = surface;
if (aCropRect.isSome()) {
// The blob is just decoded into a RasterImage and not optimized yet, so the
// _surface_ we get is a DataSourceSurface which wraps the RasterImage's
// raw buffer.
//
@@ -1212,65 +1261,80 @@ public:
{
DoCreateImageBitmapFromBlob();
return NS_OK;
}
private:
already_AddRefed<ImageBitmap> CreateImageBitmap() override
{
- RefPtr<layers::Image> data = DecodeAndCropBlob(*mBlob, mCropRect);
+ // _sourceSize_ is used to get the original size of the source image,
+ // before being cropped.
+ IntSize sourceSize;
+
+ // Keep the orignal cropping rectangle because the mCropRect might be
+ // modified in DecodeAndCropBlob().
+ Maybe<IntRect> originalCropRect = mCropRect;
+
+ RefPtr<layers::Image> data = DecodeAndCropBlob(*mBlob, mCropRect, sourceSize);
if (NS_WARN_IF(!data)) {
mPromise->MaybeRejectWithNull();
return nullptr;
}
// Create ImageBitmap object.
RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ imageBitmap->SetIsCroppingAreaOutSideOfSourceImage(sourceSize, originalCropRect);
+
return imageBitmap.forget();
}
};
class CreateImageBitmapFromBlobWorkerTask final : public WorkerSameThreadRunnable,
public CreateImageBitmapFromBlob
{
// This is a synchronous task.
class DecodeBlobInMainThreadSyncTask final : public WorkerMainThreadRunnable
{
public:
DecodeBlobInMainThreadSyncTask(WorkerPrivate* aWorkerPrivate,
Blob& aBlob,
Maybe<IntRect>& aCropRect,
- layers::Image** aImage)
+ layers::Image** aImage,
+ IntSize& aSourceSize)
: WorkerMainThreadRunnable(aWorkerPrivate,
NS_LITERAL_CSTRING("ImageBitmap :: Create Image from Blob"))
, mBlob(aBlob)
, mCropRect(aCropRect)
, mImage(aImage)
+ , mSourceSize(aSourceSize)
{
}
bool MainThreadRun() override
{
- RefPtr<layers::Image> image = DecodeAndCropBlob(mBlob, mCropRect);
+ RefPtr<layers::Image> image = DecodeAndCropBlob(mBlob, mCropRect, mSourceSize);
if (NS_WARN_IF(!image)) {
return true;
}
image.forget(mImage);
return true;
}
private:
Blob& mBlob;
Maybe<IntRect>& mCropRect;
layers::Image** mImage;
+ IntSize mSourceSize;
};
public:
CreateImageBitmapFromBlobWorkerTask(Promise* aPromise,
nsIGlobalObject* aGlobal,
mozilla::dom::Blob& aBlob,
const Maybe<IntRect>& aCropRect)
: WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
@@ -1281,37 +1345,49 @@ public:
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
return DoCreateImageBitmapFromBlob();
}
private:
already_AddRefed<ImageBitmap> CreateImageBitmap() override
{
+ // _sourceSize_ is used to get the original size of the source image,
+ // before being cropped.
+ IntSize sourceSize;
+
+ // Keep the orignal cropping rectangle because the mCropRect might be
+ // modified in DecodeAndCropBlob().
+ Maybe<IntRect> originalCropRect = mCropRect;
+
RefPtr<layers::Image> data;
ErrorResult rv;
RefPtr<DecodeBlobInMainThreadSyncTask> task =
new DecodeBlobInMainThreadSyncTask(mWorkerPrivate, *mBlob, mCropRect,
- getter_AddRefs(data));
+ getter_AddRefs(data), sourceSize);
task->Dispatch(rv); // This is a synchronous call.
if (NS_WARN_IF(rv.Failed())) {
// XXXbz does this really make sense if we're shutting down? Ah, well.
mPromise->MaybeReject(rv);
return nullptr;
}
if (NS_WARN_IF(!data)) {
mPromise->MaybeRejectWithNull();
return nullptr;
}
// Create ImageBitmap object.
RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(mGlobalObject, data);
+
+ // Set mIsCroppingAreaOutSideOfSourceImage.
+ imageBitmap->SetIsCroppingAreaOutSideOfSourceImage(sourceSize, originalCropRect);
+
return imageBitmap.forget();
}
};
static void
AsyncCreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal,
Blob& aBlob, const Maybe<IntRect>& aCropRect)
@@ -1391,21 +1467,22 @@ ImageBitmap::ReadStructuredClone(JSConte
MOZ_ASSERT(aReader);
// aParent might be null.
uint32_t picRectX_;
uint32_t picRectY_;
uint32_t picRectWidth_;
uint32_t picRectHeight_;
uint32_t isPremultipliedAlpha_;
- uint32_t dummy_;
+ uint32_t isCroppingAreaOutSideOfSourceImage_;
if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
!JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
- !JS_ReadUint32Pair(aReader, &isPremultipliedAlpha_, &dummy_)) {
+ !JS_ReadUint32Pair(aReader, &isPremultipliedAlpha_,
+ &isCroppingAreaOutSideOfSourceImage_)) {
return nullptr;
}
int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
@@ -1419,16 +1496,19 @@ ImageBitmap::ReadStructuredClone(JSConte
// JSObject* type means that JSObject* is on the stack as a raw pointer
// while destructors are running.
JS::Rooted<JS::Value> value(aCx);
{
RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
RefPtr<ImageBitmap> imageBitmap =
new ImageBitmap(aParent, img, isPremultipliedAlpha_);
+ imageBitmap->mIsCroppingAreaOutSideOfSourceImage =
+ isCroppingAreaOutSideOfSourceImage_;
+
ErrorResult error;
imageBitmap->SetPictureRect(IntRect(picRectX, picRectY,
picRectWidth, picRectHeight), error);
if (NS_WARN_IF(error.Failed())) {
error.SuppressException();
return nullptr;
}
@@ -1448,24 +1528,26 @@ ImageBitmap::WriteStructuredClone(JSStru
MOZ_ASSERT(aWriter);
MOZ_ASSERT(aImageBitmap);
const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
const uint32_t picRectWidth = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
const uint32_t picRectHeight = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
const uint32_t isPremultipliedAlpha = aImageBitmap->mIsPremultipliedAlpha ? 1 : 0;
+ const uint32_t isCroppingAreaOutSideOfSourceImage = aImageBitmap->mIsCroppingAreaOutSideOfSourceImage ? 1 : 0;
// Indexing the cloned surfaces and send the index to the receiver.
uint32_t index = aClonedSurfaces.Length();
if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight)) ||
- NS_WARN_IF(!JS_WriteUint32Pair(aWriter, isPremultipliedAlpha, 0))) {
+ NS_WARN_IF(!JS_WriteUint32Pair(aWriter, isPremultipliedAlpha,
+ isCroppingAreaOutSideOfSourceImage))) {
return false;
}
RefPtr<SourceSurface> surface =
aImageBitmap->mData->GetAsSourceSurface();
RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
RefPtr<DataSourceSurface> dstDataSurface;
{
@@ -1712,16 +1794,39 @@ ImageBitmap::MapDataInto(JSContext* aCx,
MOZ_ASSERT(aCx, "No JSContext while calling ImageBitmap::MapDataInto().");
RefPtr<Promise> promise = Promise::Create(mParent, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
+ // Check for cases that should throws.
+ // Case 1:
+ // If image bitmap was cropped to the source rectangle so that it contains any
+ // transparent black pixels (cropping area is outside of the source image),
+ // then reject promise with IndexSizeError and abort these steps.
+ if (mIsCroppingAreaOutSideOfSourceImage) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return promise.forget();
+ }
+
+ // Case 2:
+ // If the image bitmap is going to be accessed in YUV422/YUV422 series with a
+ // cropping area starts at an odd x or y coordinate.
+ if (aFormat == ImageBitmapFormat::YUV422P ||
+ aFormat == ImageBitmapFormat::YUV420P ||
+ aFormat == ImageBitmapFormat::YUV420SP_NV12 ||
+ aFormat == ImageBitmapFormat::YUV420SP_NV21) {
+ if ((mPictureRect.x & 1) || (mPictureRect.y & 1)) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return promise.forget();
+ }
+ }
+
AsyncMapDataIntoBufferSource(aCx, promise, this, aBuffer, aOffset, aFormat);
return promise.forget();
}
// ImageBitmapFactories extensions.
static SurfaceFormat
ImageFormatToSurfaceFromat(mozilla::dom::ImageBitmapFormat aFormat)
{
@@ -1997,15 +2102,19 @@ ImageBitmap::Create(nsIGlobalObject* aGl
// Create an ImageBimtap.
// Assume the data from an external buffer is not alpha-premultiplied.
RefPtr<ImageBitmap> imageBitmap = new ImageBitmap(aGlobal, data, false);
// We don't need to call SetPictureRect() here because there is no cropping
// supported and the ImageBitmap's mPictureRect is the size of the source
// image in default
+ // We don't need to set mIsCroppingAreaOutSideOfSourceImage here because there
+ // is no cropping supported and the mIsCroppingAreaOutSideOfSourceImage is
+ // false in default.
+
AsyncFulfillImageBitmapPromise(promise, imageBitmap);
return promise.forget();
}
} // namespace dom
} // namespace mozilla