diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index fa81bbb7c635..2486582bdf15 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -219,6 +219,7 @@ #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" +#include "mozilla/dom/ImageBitmap.h" #ifdef HAVE_SIDEBAR #include "mozilla/dom/ExternalBinding.h" #endif @@ -14665,3 +14666,18 @@ nsGlobalWindow::FireOnNewGlobalObject() #ifdef _WINDOWS_ #error "Never include windows.h in this file!" #endif + +already_AddRefed +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Nothing(), aRv); +} + +already_AddRefed +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv); +} diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index 9565fd85fcfc..6a93f28bdc92 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -52,6 +52,7 @@ #include "nsComponentManagerUtils.h" #include "nsSize.h" #include "nsCheapSets.h" +#include "mozilla/dom/ImageBitmapSource.h" #define DEFAULT_HOME_PAGE "www.mozilla.org" #define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage" @@ -1132,6 +1133,15 @@ public: GetContent(aCx, aRetval, aError); } + already_AddRefed + CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage, + mozilla::ErrorResult& aRv); + + already_AddRefed + CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + mozilla::ErrorResult& aRv); + // ChromeWindow bits. Do NOT call these unless your window is in // fact an nsGlobalChromeWindow. uint16_t WindowState(); diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp new file mode 100644 index 000000000000..b6466864f1a5 --- /dev/null +++ b/dom/canvas/ImageBitmap.cpp @@ -0,0 +1,1090 @@ +/* -*- 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/dom/ImageBitmapBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/gfx/2D.h" +#include "imgTools.h" +#include "libyuv.h" +#include "nsLayoutUtils.h" + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +namespace mozilla { +namespace dom { + +using namespace workers; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmap, mParent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* + * 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_. + */ +static already_AddRefed +CropDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect) +{ + MOZ_ASSERT(aSurface); + + // Calculate the size of the new SourceSurface. + const SurfaceFormat format = aSurface->GetFormat(); + const IntSize dstSize = gfx::IntSize(aCropRect.width, aCropRect.height); + const uint32_t dstStride = dstSize.width * BytesPerPixel(format); + + // Create a new SourceSurface. + RefPtr dstDataSurface = + Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride); + + if (NS_WARN_IF(!dstDataSurface)) { + return nullptr; + } + + // Copy the raw data into the newly created DataSourceSurface. + RefPtr srcDataSurface = aSurface; + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface dstMap; + if (NS_WARN_IF(!srcDataSurface->Map(DataSourceSurface::MapType::READ, &srcMap)) || + NS_WARN_IF(!dstDataSurface->Map(DataSourceSurface::MapType::WRITE, &dstMap))) { + return nullptr; + } + + uint8_t* srcBufferPtr = srcMap.mData + aCropRect.y * srcMap.mStride + + aCropRect.x * BytesPerPixel(format); + uint8_t* dstBufferPtr = dstMap.mData; + for (int i = 0; i < dstSize.height; ++i) { + memcpy(dstBufferPtr, srcBufferPtr, dstMap.mStride); + srcBufferPtr += srcMap.mStride; + dstBufferPtr += dstMap.mStride; + } + + srcDataSurface->Unmap(); + dstDataSurface->Unmap(); + + return dstDataSurface.forget(); +} + +/* + * Encapsulate the given _aSurface_ into a layers::CairoImage. + */ +static already_AddRefed +CreateImageFromSurface(SourceSurface* aSurface, ErrorResult& aRv) +{ + MOZ_ASSERT(aSurface); + + layers::CairoImage::Data cairoData; + cairoData.mSize = aSurface->GetSize(); + cairoData.mSourceSurface = aSurface; + + nsRefPtr image = new layers::CairoImage(); + + image->SetData(cairoData); + + return image.forget(); +} + +/* + * CreateImageFromRawData(), CreateSurfaceFromRawData() and + * CreateImageFromRawDataInMainThreadSyncTask are helpers for + * create-from-ImageData case + */ +static already_AddRefed +CreateSurfaceFromRawData(const gfx::IntSize& aSize, + uint32_t aStride, + gfx::SurfaceFormat aFormat, + uint8_t* aBuffer, + uint32_t aBufferLength, + const Maybe& aCropRect, + ErrorResult& aRv) +{ + MOZ_ASSERT(!aSize.IsEmpty()); + MOZ_ASSERT(aBuffer); + + // Wrap the source buffer into a SourceSurface. + RefPtr dataSurface = + Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize, aFormat); + + if (NS_WARN_IF(!dataSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + 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 result = CropDataSourceSurface(dataSurface, cropRect); + + if (NS_WARN_IF(!result)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + return result.forget(); +} + +static already_AddRefed +CreateImageFromRawData(const gfx::IntSize& aSize, + uint32_t aStride, + gfx::SurfaceFormat aFormat, + uint8_t* aBuffer, + uint32_t aBufferLength, + const Maybe& aCropRect, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Copy and crop the source buffer into a SourceSurface. + RefPtr rgbaSurface = + CreateSurfaceFromRawData(aSize, aStride, aFormat, + aBuffer, aBufferLength, + aCropRect, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Convert RGBA to BGRA + RefPtr rgbaDataSurface = rgbaSurface->GetDataSurface(); + RefPtr bgraDataSurface = + Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(), + SurfaceFormat::B8G8R8A8, + rgbaDataSurface->Stride()); + + DataSourceSurface::MappedSurface rgbaMap; + DataSourceSurface::MappedSurface bgraMap; + + if (NS_WARN_IF(!rgbaDataSurface->Map(DataSourceSurface::MapType::READ, &rgbaMap)) || + NS_WARN_IF(!bgraDataSurface->Map(DataSourceSurface::MapType::WRITE, &bgraMap))) { + return nullptr; + } + + libyuv::ABGRToARGB(rgbaMap.mData, rgbaMap.mStride, + bgraMap.mData, bgraMap.mStride, + bgraDataSurface->GetSize().width, + bgraDataSurface->GetSize().height); + + rgbaDataSurface->Unmap(); + bgraDataSurface->Unmap(); + + // Create an Image from the BGRA SourceSurface. + nsRefPtr image = CreateImageFromSurface(bgraDataSurface, aRv); + + return image.forget(); +} + +/* + * This is a synchronous task. + * This class is used to create a layers::CairoImage from raw data in the main + * thread. While creating an ImageBitmap from an ImageData, we need to create + * a SouceSurface from the ImageData's raw data and then set the SourceSurface + * into a layers::CairoImage. However, the layers::CairoImage asserts the + * setting operation in the main thread, so if we are going to create an + * ImageBitmap from an ImageData off the main thread, we post an event to the + * main thread to create a layers::CairoImage 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& aCropRect, + ErrorResult& aError, + layers::Image** aImage) + : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate()) + , mImage(aImage) + , mBuffer(aBuffer) + , mBufferLength(aBufferLength) + , mStride(aStride) + , mFormat(aFormat) + , mSize(aSize) + , mCropRect(aCropRect) + , mError(aError) + { + } + + bool MainThreadRun() override + { + nsRefPtr image = + CreateImageFromRawData(mSize, mStride, mFormat, + mBuffer, mBufferLength, + mCropRect, + mError); + + if (NS_WARN_IF(mError.Failed())) { + 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& mCropRect; + ErrorResult& mError; +}; + +static bool +CheckSecurityForHTMLElements(bool aIsWriteOnly, bool aCORSUsed, nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(aPrincipal); + + if (aIsWriteOnly) { + return false; + } + + if (!aCORSUsed) { + nsIGlobalObject* incumbentSettingsObject = GetIncumbentGlobal(); + if (NS_WARN_IF(!incumbentSettingsObject)) { + return false; + } + + nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull(); + if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) { + return false; + } + } + + return true; +} + +static bool +CheckSecurityForHTMLElements(const nsLayoutUtils::SurfaceFromElementResult& aRes) +{ + return CheckSecurityForHTMLElements(aRes.mIsWriteOnly, aRes.mCORSUsed, aRes.mPrincipal); +} + +/* + * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the + * security checking. + */ +template +static already_AddRefed +GetSurfaceFromElement(nsIGlobalObject* aGlobal, HTMLElementType& aElement, ErrorResult& aRv) +{ + nsLayoutUtils::SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME); + + // check origin-clean + if (!CheckSecurityForHTMLElements(res)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + if (NS_WARN_IF(!res.mSourceSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + RefPtr surface(res.mSourceSurface); + return surface.forget(); +} + +/* + * The specification doesn't allow to create an ImegeBitmap from a vector image. + * This function is used to check if the given HTMLImageElement contains a + * raster image. + */ +static bool +HasRasterImage(HTMLImageElement& aImageEl) +{ + nsresult rv; + + nsCOMPtr imgRequest; + rv = aImageEl.GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(imgRequest)); + if (NS_SUCCEEDED(rv) && imgRequest) { + nsCOMPtr imgContainer; + rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); + if (NS_SUCCEEDED(rv) && imgContainer && + imgContainer->GetType() == imgIContainer::TYPE_RASTER) { + return true; + } + } + + return false; +} + +ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData) + : mParent(aGlobal) + , mData(aData) + , mSurface(nullptr) + , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height) +{ + MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor."); +} + +ImageBitmap::~ImageBitmap() +{ +} + +JSObject* +ImageBitmap::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return ImageBitmapBinding::Wrap(aCx, this, aGivenProto); +} + +void +ImageBitmap::SetPictureRect(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.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.y = checkedY.value(); + rect.height = -(rect.height); + } + + mPictureRect = rect; +} + +already_AddRefed +ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget) +{ + MOZ_ASSERT(aTarget); + + if (!mSurface) { + mSurface = mData->GetAsSourceSurface(); + } + + if (!mSurface) { + return nullptr; + } + + RefPtr 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 surface(mSurface); + return surface.forget(); + } + + IntPoint dest(std::max(0, surfPortion.X() - mPictureRect.X()), + std::max(0, surfPortion.Y() - mPictureRect.Y())); + + // Do not initialize this target with mPictureRect.Size(). + // In the Windows8 D2D1 backend, it might trigger "partial upload" from a + // non-SourceSurfaceD2D1 surface to a D2D1Image in the following + // CopySurface() step. However, the "partial upload" only supports uploading + // a rectangle starts from the upper-left point, which means it cannot + // upload an arbitrary part of the source surface and this causes problems + // if the mPictureRect is not starts from the upper-left point. + target = target->CreateSimilarDrawTarget(mSurface->GetSize(), + target->GetFormat()); + + if (!target) { + mSurface = nullptr; + RefPtr surface(mSurface); + return surface.forget(); + } + + // Make mCropRect match new surface we've cropped to + mPictureRect.MoveTo(0, 0); + target->CopySurface(mSurface, surfPortion, dest); + mSurface = target->Snapshot(); + } + + // 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 surface(mSurface); + return surface.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Check if the image element is completely available or not. + if (!aImageEl.Complete()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Check if the image element is a bitmap (e.g. it's a vector graphic) or not. + if (!HasRasterImage(aImageEl)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Get the SourceSurface out from the image element and then do security + // checking. + RefPtr surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Create ImageBitmap. + nsRefPtr data = CreateImageFromSurface(surface, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Check network state. + if (aVideoEl.NetworkState() == HTMLMediaElement::NETWORK_EMPTY) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Check ready state. + // Cannot be HTMLMediaElement::HAVE_NOTHING or HTMLMediaElement::HAVE_METADATA. + if (aVideoEl.ReadyState() <= HTMLMediaElement::HAVE_METADATA) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Check security. + nsCOMPtr principal = aVideoEl.GetCurrentPrincipal(); + bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE; + if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + // Create ImageBitmap. + ImageContainer *container = aVideoEl.GetImageContainer(); + + if (!container) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + AutoLockImage lockImage(container); + layers::Image* data = lockImage.GetImage(); + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl, + const Maybe& aCropRect, ErrorResult& aRv) +{ + if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + RefPtr surface = GetSurfaceFromElement(aGlobal, aCanvasEl, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Crop the source surface if needed. + RefPtr croppedSurface; + IntRect cropRect = aCropRect.valueOr(IntRect()); + + // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot + // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy + // of the rendering context. We handle cropping in this case. + if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 || + aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) && + aCropRect.isSome()) { + // The _surface_ must be a DataSourceSurface. + MOZ_ASSERT(surface->GetType() == SurfaceType::DATA, + "The snapshot SourceSurface from WebGL rendering contest is not \ + DataSourceSurface."); + RefPtr dataSurface = surface->GetDataSurface(); + croppedSurface = CropDataSourceSurface(dataSurface, cropRect); + cropRect.MoveTo(0, 0); + } + else { + croppedSurface = surface; + } + + if (NS_WARN_IF(!croppedSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // Create an Image from the SourceSurface. + nsRefPtr data = CreateImageFromSurface(croppedSurface, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(cropRect, aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Copy data into SourceSurface. + dom::Uint8ClampedArray array; + DebugOnly inited = array.Init(aImageData.GetDataObject()); + MOZ_ASSERT(inited); + + array.ComputeLengthAndData(); + const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8; + const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT); + const uint32_t imageWidth = aImageData.Width(); + const uint32_t imageHeight = aImageData.Height(); + const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL; + const uint32_t dataLength = array.Length(); + const gfx::IntSize imageSize(imageWidth, imageHeight); + + // Check the ImageData is neutered or not. + if (imageWidth == 0 || imageHeight == 0 || + (imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Create and Crop the raw data into a layers::Image + nsRefPtr data; + if (NS_IsMainThread()) { + data = CreateImageFromRawData(imageSize, imageStride, FORMAT, + array.Data(), dataLength, + aCropRect, aRv); + } else { + nsRefPtr task + = new CreateImageFromRawDataInMainThreadSyncTask(array.Data(), + dataLength, + imageStride, + FORMAT, + imageSize, + aCropRect, + aRv, + getter_AddRefs(data)); + task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Create an ImageBimtap. + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // The cropping information has been handled in the CreateImageFromRawData() + // function. + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Check origin-clean. + if (aCanvasCtx.GetCanvas()->IsWriteOnly()) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + RefPtr 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; + } + + nsRefPtr data = CreateImageFromSurface(surface, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap, + const Maybe& aCropRect, ErrorResult& aRv) +{ + if (!aImageBitmap.mData) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + nsRefPtr data = aImageBitmap.mData; + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // 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: + nsRefPtr mPromise; + nsRefPtr mImageBitmap; +}; + +class FulfillImageBitmapPromiseTask final : public nsRunnable, + public FulfillImageBitmapPromise +{ +public: + FulfillImageBitmapPromiseTask(Promise* aPromise, ImageBitmap* aImageBitmap) + : FulfillImageBitmapPromise(aPromise, aImageBitmap) + { + } + + NS_IMETHOD Run() override + { + DoFulfillImageBitmapPromise(); + return NS_OK; + } +}; + +class FulfillImageBitmapPromiseWorkerTask final : public WorkerSameThreadRunnable, + public FulfillImageBitmapPromise +{ +public: + FulfillImageBitmapPromiseWorkerTask(Promise* aPromise, ImageBitmap* aImageBitmap) + : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()), + FulfillImageBitmapPromise(aPromise, aImageBitmap) + { + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + DoFulfillImageBitmapPromise(); + return true; + } +}; + +static void +AsyncFulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap) +{ + if (NS_IsMainThread()) { + nsCOMPtr task = + new FulfillImageBitmapPromiseTask(aPromise, aImageBitmap); + NS_DispatchToCurrentThread(task); // Actually, to the main-thread. + } else { + nsRefPtr task = + new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap); + task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); // Actually, to the current worker-thread. + } +} + +static already_AddRefed +DecodeBlob(Blob& aBlob, ErrorResult& aRv) +{ + // Get the internal stream of the blob. + nsCOMPtr stream; + aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Get the MIME type string of the blob. + // The type will be checked in the DecodeImage() method. + nsAutoString mimeTypeUTF16; + aBlob.GetType(mimeTypeUTF16); + + // Get the Component object. + nsCOMPtr imgtool = do_GetService(NS_IMGTOOLS_CID); + if (NS_WARN_IF(!imgtool)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // Decode image. + NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeTypeUTF16); // NS_ConvertUTF16toUTF8 ---|> nsAutoCString + nsCOMPtr imgContainer; + nsresult rv = imgtool->DecodeImage(stream, mimeTypeUTF8, getter_AddRefs(imgContainer)); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + // Get the surface out. + uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_WANT_DATA_SURFACE; + uint32_t whichFrame = imgIContainer::FRAME_FIRST; + RefPtr surface = imgContainer->GetFrame(whichFrame, frameFlags); + + if (NS_WARN_IF(!surface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + return surface.forget(); +} + +static already_AddRefed +DecodeAndCropBlob(Blob& aBlob, Maybe& aCropRect, ErrorResult& aRv) +{ + // Decode the blob into a SourceSurface. + RefPtr surface = DecodeBlob(aBlob, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Crop the source surface if needed. + RefPtr 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. + MOZ_ASSERT(surface->GetType() == SurfaceType::DATA, + "The SourceSurface from just decoded Blob is not DataSourceSurface."); + RefPtr dataSurface = surface->GetDataSurface(); + croppedSurface = CropDataSourceSurface(dataSurface, aCropRect.ref()); + aCropRect->MoveTo(0, 0); + } + + if (NS_WARN_IF(!croppedSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // Create an Image from the source surface. + nsRefPtr image = CreateImageFromSurface(croppedSurface, aRv); + + return image.forget(); +} + +class CreateImageBitmapFromBlob +{ +protected: + CreateImageBitmapFromBlob(Promise* aPromise, + nsIGlobalObject* aGlobal, + Blob& aBlob, + const Maybe& aCropRect) + : mPromise(aPromise), + mGlobalObject(aGlobal), + mBlob(&aBlob), + mCropRect(aCropRect) + { + } + + virtual ~CreateImageBitmapFromBlob() + { + } + + void DoCreateImageBitmapFromBlob(ErrorResult& aRv) + { + nsRefPtr imageBitmap = CreateImageBitmap(aRv); + + // handle errors while creating ImageBitmap + // (1) error occurs during reading of the object + // (2) the image data is not in a supported file format + // (3) the image data is corrupted + // All these three cases should reject promise with null value + if (aRv.Failed()) { + mPromise->MaybeReject(aRv); + return; + } + + if (imageBitmap && mCropRect.isSome()) { + imageBitmap->SetPictureRect(mCropRect.ref(), aRv); + + if (aRv.Failed()) { + mPromise->MaybeReject(aRv); + return; + } + } + + mPromise->MaybeResolve(imageBitmap); + return; + } + + virtual already_AddRefed CreateImageBitmap(ErrorResult& aRv) = 0; + + nsRefPtr mPromise; + nsCOMPtr mGlobalObject; + RefPtr mBlob; + Maybe mCropRect; +}; + +class CreateImageBitmapFromBlobTask final : public nsRunnable, + public CreateImageBitmapFromBlob +{ +public: + CreateImageBitmapFromBlobTask(Promise* aPromise, + nsIGlobalObject* aGlobal, + Blob& aBlob, + const Maybe& aCropRect) + :CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect) + { + } + + NS_IMETHOD Run() override + { + ErrorResult error; + DoCreateImageBitmapFromBlob(error); + return NS_OK; + } + +private: + already_AddRefed CreateImageBitmap(ErrorResult& aRv) override + { + nsRefPtr data = DecodeAndCropBlob(*mBlob, mCropRect, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Create ImageBitmap object. + nsRefPtr imageBitmap = new ImageBitmap(mGlobalObject, data); + 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& aCropRect, + ErrorResult& aError, + layers::Image** aImage) + : WorkerMainThreadRunnable(aWorkerPrivate) + , mBlob(aBlob) + , mCropRect(aCropRect) + , mError(aError) + , mImage(aImage) + { + } + + bool MainThreadRun() override + { + nsRefPtr image = DecodeAndCropBlob(mBlob, mCropRect, mError); + + if (NS_WARN_IF(mError.Failed())) { + return false; + } + + image.forget(mImage); + + return true; + } + + private: + Blob& mBlob; + Maybe& mCropRect; + ErrorResult& mError; + layers::Image** mImage; + }; + +public: + CreateImageBitmapFromBlobWorkerTask(Promise* aPromise, + nsIGlobalObject* aGlobal, + mozilla::dom::Blob& aBlob, + const Maybe& aCropRect) + : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()), + CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect) + { + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + ErrorResult error; + DoCreateImageBitmapFromBlob(error); + return !(error.Failed()); + } + +private: + already_AddRefed CreateImageBitmap(ErrorResult& aRv) override + { + nsRefPtr data; + + nsRefPtr task = + new DecodeBlobInMainThreadSyncTask(mWorkerPrivate, *mBlob, mCropRect, + aRv, getter_AddRefs(data)); + task->Dispatch(mWorkerPrivate->GetJSContext()); // This is a synchronous call. + + if (NS_WARN_IF(aRv.Failed())) { + mPromise->MaybeReject(aRv); + return nullptr; + } + + // Create ImageBitmap object. + nsRefPtr imageBitmap = new ImageBitmap(mGlobalObject, data); + return imageBitmap.forget(); + } + +}; + +static void +AsyncCreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal, + Blob& aBlob, const Maybe& aCropRect) +{ + if (NS_IsMainThread()) { + nsCOMPtr task = + new CreateImageBitmapFromBlobTask(aPromise, aGlobal, aBlob, aCropRect); + NS_DispatchToCurrentThread(task); // Actually, to the main-thread. + } else { + nsRefPtr task = + new CreateImageBitmapFromBlobWorkerTask(aPromise, aGlobal, aBlob, aCropRect); + task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); // Actually, to the current worker-thread. + } +} + +/* static */ already_AddRefed +ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc, + const Maybe& aCropRect, ErrorResult& aRv) +{ + MOZ_ASSERT(aGlobal); + + nsRefPtr promise = Promise::Create(aGlobal, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return promise.forget(); + } + + nsRefPtr imageBitmap; + + if (aSrc.IsHTMLImageElement()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from HTMLImageElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLImageElement(), aCropRect, aRv); + } else if (aSrc.IsHTMLVideoElement()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from HTMLVideoElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(), aCropRect, aRv); + } else if (aSrc.IsHTMLCanvasElement()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from HTMLCanvasElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(), aCropRect, aRv); + } else if (aSrc.IsImageData()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect, aRv); + } else if (aSrc.IsCanvasRenderingContext2D()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from CanvasRenderingContext2D off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(), aCropRect, aRv); + } else if (aSrc.IsImageBitmap()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect, aRv); + } else if (aSrc.IsBlob()) { + AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(), aCropRect); + return promise.forget(); + } else { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + } + + if (!aRv.Failed()) { + AsyncFulfillImageBitmapPromise(promise, imageBitmap); + } + + return promise.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/canvas/ImageBitmap.h b/dom/canvas/ImageBitmap.h new file mode 100644 index 000000000000..cdf311a48091 --- /dev/null +++ b/dom/canvas/ImageBitmap.h @@ -0,0 +1,162 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_ImageBitmap_h +#define mozilla_dom_ImageBitmap_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/ImageBitmapSource.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/Maybe.h" +#include "nsCycleCollectionParticipant.h" + +struct JSContext; + +namespace mozilla { + +class ErrorResult; + +namespace gfx { +class SourceSurface; +} + +namespace layers { +class Image; +} + +namespace dom { + +class CanvasRenderingContext2D; +class File; +class HTMLCanvasElement; +class HTMLImageElement; +class HTMLVideoElement; +class ImageData; +class Promise; +class CreateImageBitmapFromBlob; +class CreateImageBitmapFromBlobTask; +class CreateImageBitmapFromBlobWorkerTask; + +/* + * ImageBitmap is an opaque handler to several kinds of image-like objects from + * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to + * CanvasRenderingContext2D and Image Blob. + * + * An ImageBitmap could be painted to a canvas element. + * + * Generally, an ImageBitmap only keeps a reference to its source object's + * buffer, but if the source object is an ImageData, an Blob or a + * HTMLCanvasElement with WebGL rendering context, the ImageBitmap copy the + * source object's buffer. + */ +class ImageBitmap final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageBitmap) + + nsCOMPtr GetParentObject() const { return mParent; } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + uint32_t Width() const + { + return mPictureRect.Width(); + } + + uint32_t Height() const + { + return mPictureRect.Height(); + } + + /* + * The PrepareForDrawTarget() might return null if the mPictureRect does not + * intersect with the size of mData. + */ + already_AddRefed + PrepareForDrawTarget(gfx::DrawTarget* aTarget); + + static already_AddRefed + Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc, + const Maybe& aCropRect, ErrorResult& aRv); + + friend CreateImageBitmapFromBlob; + friend CreateImageBitmapFromBlobTask; + friend CreateImageBitmapFromBlobWorkerTask; + +protected: + + ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData); + + virtual ~ImageBitmap(); + + void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap, + const Maybe& aCropRect, ErrorResult& aRv); + + nsCOMPtr mParent; + + /* + * The mData is the data buffer of an ImageBitmap, so the mData must not be + * null. + * + * The mSurface is a cache for drawing the ImageBitmap onto a + * HTMLCanvasElement. The mSurface is null while the ImageBitmap is created + * and then will be initialized while the PrepareForDrawTarget() method is + * called first time. + * + * The mSurface might just be a reference to the same data buffer of the mData + * if the are of mPictureRect is just the same as the mData's size. Or, it is + * a independent data buffer which is copied and cropped form the mData's data + * buffer. + */ + nsRefPtr mData; + RefPtr mSurface; + + /* + * The mPictureRect is the size of the source image in default, however, if + * users specify the cropping area while creating an ImageBitmap, then this + * mPictureRect is the cropping area. + * + * Note that if the CreateInternal() copies and crops data from the source + * image, then this mPictureRect is just the size of the final mData. + * + * The mPictureRect will be used at PrepareForDrawTarget() while user is going + * to draw this ImageBitmap into a HTMLCanvasElement. + */ + gfx::IntRect mPictureRect; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ImageBitmap_h + + diff --git a/dom/canvas/ImageBitmapSource.h b/dom/canvas/ImageBitmapSource.h new file mode 100644 index 000000000000..d81990715e75 --- /dev/null +++ b/dom/canvas/ImageBitmapSource.h @@ -0,0 +1,21 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_ImageBitmapSource_h +#define mozilla_dom_ImageBitmapSource_h + +namespace mozilla { +namespace dom { + +// So we don't have to forward declare this elsewhere. +class HTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrBlobOrImageDataOrCanvasRenderingContext2DOrImageBitmap; +typedef HTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrBlobOrImageDataOrCanvasRenderingContext2DOrImageBitmap + ImageBitmapSource; + +} +} + +#endif diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index db6c4a2acff7..d8384e884f50 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -29,6 +29,8 @@ EXPORTS.mozilla.dom += [ 'CanvasPattern.h', 'CanvasRenderingContext2D.h', 'CanvasUtils.h', + 'ImageBitmap.h', + 'ImageBitmapSource.h', 'ImageData.h', 'TextMetrics.h', 'WebGLVertexArrayObject.h', @@ -44,6 +46,7 @@ UNIFIED_SOURCES += [ 'CanvasUtils.cpp', 'DocumentRendererChild.cpp', 'DocumentRendererParent.cpp', + 'ImageBitmap.cpp', 'ImageData.cpp', ] @@ -146,6 +149,7 @@ include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ + '../workers', '/dom/base', '/dom/html', '/dom/svg', @@ -156,6 +160,7 @@ LOCAL_INCLUDES += [ '/layout/generic', '/layout/style', '/layout/xul', + '/media/libyuv/include', ] CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index 4593ca2bf567..4631809d01be 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -749,9 +749,12 @@ GetCanvasContextType(const nsAString& str, CanvasContextType* const out_type) static already_AddRefed CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas) { + MOZ_ASSERT(contextType != CanvasContextType::NoContext); nsRefPtr ret; switch (contextType) { + case CanvasContextType::NoContext: + break; case CanvasContextType::Canvas2D: Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); ret = new CanvasRenderingContext2D(); diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index dd0073c3a264..b6387c8763f1 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -36,6 +36,7 @@ class HTMLCanvasPrintState; class PrintCallback; enum class CanvasContextType : uint8_t { + NoContext, Canvas2D, WebGL1, WebGL2 @@ -262,6 +263,10 @@ public: void ResetPrintCallback(); HTMLCanvasElement* GetOriginalCanvas(); + + CanvasContextType GetCurrentContextType() { + return mCurrentContextType; + } }; class HTMLCanvasPrintState final : public nsWrapperCache diff --git a/dom/webidl/ImageBitmap.webidl b/dom/webidl/ImageBitmap.webidl new file mode 100644 index 000000000000..a257d63ff3e6 --- /dev/null +++ b/dom/webidl/ImageBitmap.webidl @@ -0,0 +1,32 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://html.spec.whatwg.org/multipage/webappapis.html#images + */ + +typedef (HTMLImageElement or + HTMLVideoElement or + HTMLCanvasElement or + Blob or + ImageData or + CanvasRenderingContext2D or + ImageBitmap) ImageBitmapSource; + +[Exposed=(Window,Worker)] +interface ImageBitmap { + [Constant] + readonly attribute unsigned long width; + [Constant] + readonly attribute unsigned long height; +}; + +[NoInterfaceObject, Exposed=(Window,Worker)] +interface ImageBitmapFactories { + [Throws] + Promise createImageBitmap(ImageBitmapSource aImage); + [Throws] + Promise createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh); +}; diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl index 6138a30be70b..05c984a4e0c4 100644 --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -483,3 +483,4 @@ interface ChromeWindow { Window implements ChromeWindow; Window implements GlobalFetch; +Window implements ImageBitmapFactories; diff --git a/dom/webidl/WorkerGlobalScope.webidl b/dom/webidl/WorkerGlobalScope.webidl index b7f397154f4d..f56eb40802a5 100644 --- a/dom/webidl/WorkerGlobalScope.webidl +++ b/dom/webidl/WorkerGlobalScope.webidl @@ -48,6 +48,7 @@ WorkerGlobalScope implements WindowTimers; WorkerGlobalScope implements WindowBase64; WorkerGlobalScope implements GlobalFetch; WorkerGlobalScope implements IDBEnvironment; +WorkerGlobalScope implements ImageBitmapFactories; // Not implemented yet: bug 1072107. // WorkerGlobalScope implements FontFaceSource; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 34ea3a8d5490..a410f72452e2 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -259,6 +259,7 @@ WEBIDL_FILES = [ 'IDBRequest.webidl', 'IDBTransaction.webidl', 'IDBVersionChangeEvent.webidl', + 'ImageBitmap.webidl', 'ImageCapture.webidl', 'ImageData.webidl', 'ImageDocument.webidl', diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 9f2372dd35cf..6b1f752100d4 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" @@ -386,6 +387,21 @@ WorkerGlobalScope::GetIndexedDB(ErrorResult& aErrorResult) return indexedDB.forget(); } +already_AddRefed +WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Nothing(), aRv); +} + +already_AddRefed +WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv); +} + DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate) : WorkerGlobalScope(aWorkerPrivate) { diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index d00b92c6ab77..69c25c32a358 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -12,6 +12,7 @@ #include "mozilla/dom/Headers.h" #include "mozilla/dom/RequestBinding.h" #include "nsWeakReference.h" +#include "mozilla/dom/ImageBitmapSource.h" namespace mozilla { namespace dom { @@ -153,6 +154,14 @@ public: already_AddRefed GetCaches(ErrorResult& aRv); + + already_AddRefed + CreateImageBitmap(const ImageBitmapSource& aImage, ErrorResult& aRv); + + already_AddRefed + CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv); }; class DedicatedWorkerGlobalScope final : public WorkerGlobalScope