no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / canvas / ImageBitmap.cpp
blobf0bbb46543d252f3c28b2c481014ab3de15c1d2a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ImageBitmap.h"
8 #include "mozilla/CheckedInt.h"
9 #include "mozilla/dom/BlobImpl.h"
10 #include "mozilla/dom/CanvasRenderingContext2D.h"
11 #include "mozilla/dom/CanvasUtils.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/HTMLCanvasElement.h"
14 #include "mozilla/dom/HTMLImageElement.h"
15 #include "mozilla/dom/HTMLMediaElementBinding.h"
16 #include "mozilla/dom/HTMLVideoElement.h"
17 #include "mozilla/dom/ImageBitmapBinding.h"
18 #include "mozilla/dom/OffscreenCanvas.h"
19 #include "mozilla/dom/Promise.h"
20 #include "mozilla/dom/StructuredCloneTags.h"
21 #include "mozilla/dom/SVGImageElement.h"
22 #include "mozilla/dom/WorkerPrivate.h"
23 #include "mozilla/dom/WorkerRef.h"
24 #include "mozilla/dom/WorkerRunnable.h"
25 #include "mozilla/dom/VideoFrame.h"
26 #include "mozilla/gfx/2D.h"
27 #include "mozilla/gfx/Logging.h"
28 #include "mozilla/gfx/Scale.h"
29 #include "mozilla/gfx/Swizzle.h"
30 #include "mozilla/Mutex.h"
31 #include "mozilla/ScopeExit.h"
32 #include "nsGlobalWindowInner.h"
33 #include "nsIAsyncInputStream.h"
34 #include "nsNetUtil.h"
35 #include "nsLayoutUtils.h"
36 #include "nsStreamUtils.h"
37 #include "imgLoader.h"
38 #include "imgTools.h"
40 using namespace mozilla::gfx;
41 using namespace mozilla::layers;
42 using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
43 using mozilla::dom::HTMLMediaElement_Binding::NETWORK_EMPTY;
45 namespace mozilla::dom {
47 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmap, mParent)
48 NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap)
49 NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap)
50 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap)
51 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
52 NS_INTERFACE_MAP_ENTRY(nsISupports)
53 NS_INTERFACE_MAP_END
55 class ImageBitmapShutdownObserver;
57 static StaticMutex sShutdownMutex;
58 static ImageBitmapShutdownObserver* sShutdownObserver = nullptr;
60 class SendShutdownToWorkerThread : public MainThreadWorkerControlRunnable {
61 public:
62 explicit SendShutdownToWorkerThread(ImageBitmap* aImageBitmap)
63 : MainThreadWorkerControlRunnable(GetCurrentThreadWorkerPrivate()),
64 mImageBitmap(aImageBitmap) {
65 MOZ_ASSERT(GetCurrentThreadWorkerPrivate());
68 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
69 if (mImageBitmap) {
70 mImageBitmap->OnShutdown();
71 mImageBitmap = nullptr;
73 return true;
76 ImageBitmap* mImageBitmap;
79 /* This class observes shutdown notifications and sends that notification
80 * to the worker thread if the image bitmap is on a worker thread.
82 class ImageBitmapShutdownObserver final : public nsIObserver {
83 public:
84 explicit ImageBitmapShutdownObserver() {
85 sShutdownMutex.AssertCurrentThreadOwns();
86 if (NS_IsMainThread()) {
87 RegisterObserver();
88 return;
91 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
92 MOZ_ASSERT(workerPrivate);
93 auto* mainThreadEventTarget = workerPrivate->MainThreadEventTarget();
94 MOZ_ASSERT(mainThreadEventTarget);
95 RefPtr<ImageBitmapShutdownObserver> self = this;
96 nsCOMPtr<nsIRunnable> r =
97 NS_NewRunnableFunction("ImageBitmapShutdownObserver::RegisterObserver",
98 [self]() { self->RegisterObserver(); });
99 mainThreadEventTarget->Dispatch(r.forget());
102 void RegisterObserver() {
103 MOZ_ASSERT(NS_IsMainThread());
104 nsContentUtils::RegisterShutdownObserver(this);
107 already_AddRefed<SendShutdownToWorkerThread> Track(
108 ImageBitmap* aImageBitmap) {
109 sShutdownMutex.AssertCurrentThreadOwns();
110 MOZ_ASSERT(!mBitmaps.Contains(aImageBitmap));
112 RefPtr<SendShutdownToWorkerThread> runnable = nullptr;
113 if (!NS_IsMainThread()) {
114 runnable = new SendShutdownToWorkerThread(aImageBitmap);
117 RefPtr<SendShutdownToWorkerThread> retval = runnable;
118 mBitmaps.Insert(aImageBitmap);
119 return retval.forget();
122 void Untrack(ImageBitmap* aImageBitmap) {
123 sShutdownMutex.AssertCurrentThreadOwns();
124 MOZ_ASSERT(mBitmaps.Contains(aImageBitmap));
126 mBitmaps.Remove(aImageBitmap);
129 NS_DECL_THREADSAFE_ISUPPORTS
130 NS_DECL_NSIOBSERVER
131 private:
132 ~ImageBitmapShutdownObserver() = default;
134 nsTHashSet<ImageBitmap*> mBitmaps;
137 NS_IMPL_ISUPPORTS(ImageBitmapShutdownObserver, nsIObserver)
139 NS_IMETHODIMP
140 ImageBitmapShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
141 const char16_t* aData) {
142 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
143 StaticMutexAutoLock lock(sShutdownMutex);
145 for (const auto& bitmap : mBitmaps) {
146 const auto& runnable = bitmap->mShutdownRunnable;
147 if (runnable) {
148 runnable->Dispatch();
149 } else {
150 bitmap->OnShutdown();
154 nsContentUtils::UnregisterShutdownObserver(this);
156 sShutdownObserver = nullptr;
159 return NS_OK;
163 * If either aRect.width or aRect.height are negative, then return a new IntRect
164 * which represents the same rectangle as the aRect does but with positive width
165 * and height.
167 static IntRect FixUpNegativeDimension(const IntRect& aRect, ErrorResult& aRv) {
168 gfx::IntRect rect = aRect;
170 // fix up negative dimensions
171 if (rect.width < 0) {
172 CheckedInt32 checkedX = CheckedInt32(rect.x) + rect.width;
174 if (!checkedX.isValid()) {
175 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
176 return rect;
179 rect.x = checkedX.value();
180 rect.width = -(rect.width);
183 if (rect.height < 0) {
184 CheckedInt32 checkedY = CheckedInt32(rect.y) + rect.height;
186 if (!checkedY.isValid()) {
187 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
188 return rect;
191 rect.y = checkedY.value();
192 rect.height = -(rect.height);
195 return rect;
199 * This helper function copies the data of the given DataSourceSurface,
200 * _aSurface_, in the given area, _aCropRect_, into a new DataSourceSurface.
201 * This might return null if it can not create a new SourceSurface or it cannot
202 * read data from the given _aSurface_.
204 * Warning: Even though the area of _aCropRect_ is just the same as the size of
205 * _aSurface_, this function still copy data into a new
206 * DataSourceSurface.
208 static already_AddRefed<DataSourceSurface> CropAndCopyDataSourceSurface(
209 DataSourceSurface* aSurface, const IntRect& aCropRect) {
210 MOZ_ASSERT(aSurface);
212 // Check the aCropRect
213 ErrorResult error;
214 const IntRect positiveCropRect = FixUpNegativeDimension(aCropRect, error);
215 if (NS_WARN_IF(error.Failed())) {
216 error.SuppressException();
217 return nullptr;
220 // Calculate the size of the new SourceSurface.
221 // We cannot keep using aSurface->GetFormat() to create new DataSourceSurface,
222 // since it might be SurfaceFormat::B8G8R8X8 which does not handle opacity,
223 // however the specification explicitly define that "If any of the pixels on
224 // this rectangle are outside the area where the input bitmap was placed, then
225 // they will be transparent black in output."
226 // So, instead, we force the output format to be SurfaceFormat::B8G8R8A8.
227 const SurfaceFormat format = SurfaceFormat::B8G8R8A8;
228 const int bytesPerPixel = BytesPerPixel(format);
229 const IntSize dstSize =
230 IntSize(positiveCropRect.width, positiveCropRect.height);
231 const uint32_t dstStride = dstSize.width * bytesPerPixel;
233 // Create a new SourceSurface.
234 RefPtr<DataSourceSurface> dstDataSurface =
235 Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride,
236 true);
238 if (NS_WARN_IF(!dstDataSurface)) {
239 return nullptr;
242 // Only do copying and cropping when the positiveCropRect intersects with
243 // the size of aSurface.
244 const IntRect surfRect(IntPoint(0, 0), aSurface->GetSize());
245 if (surfRect.Intersects(positiveCropRect)) {
246 const IntRect surfPortion = surfRect.Intersect(positiveCropRect);
247 const IntPoint dest(std::max(0, surfPortion.X() - positiveCropRect.X()),
248 std::max(0, surfPortion.Y() - positiveCropRect.Y()));
250 // Copy the raw data into the newly created DataSourceSurface.
251 DataSourceSurface::ScopedMap srcMap(aSurface, DataSourceSurface::READ);
252 DataSourceSurface::ScopedMap dstMap(dstDataSurface,
253 DataSourceSurface::WRITE);
254 if (NS_WARN_IF(!srcMap.IsMapped()) || NS_WARN_IF(!dstMap.IsMapped())) {
255 return nullptr;
258 uint8_t* srcBufferPtr = srcMap.GetData() +
259 surfPortion.y * srcMap.GetStride() +
260 surfPortion.x * bytesPerPixel;
261 uint8_t* dstBufferPtr =
262 dstMap.GetData() + dest.y * dstMap.GetStride() + dest.x * bytesPerPixel;
263 CheckedInt<uint32_t> copiedBytesPerRaw =
264 CheckedInt<uint32_t>(surfPortion.width) * bytesPerPixel;
265 if (!copiedBytesPerRaw.isValid()) {
266 return nullptr;
269 for (int i = 0; i < surfPortion.height; ++i) {
270 memcpy(dstBufferPtr, srcBufferPtr, copiedBytesPerRaw.value());
271 srcBufferPtr += srcMap.GetStride();
272 dstBufferPtr += dstMap.GetStride();
276 return dstDataSurface.forget();
280 * This helper function scales the data of the given DataSourceSurface,
281 * _aSurface_, in the given area, _aCropRect_, into a new DataSourceSurface.
282 * This might return null if it can not create a new SourceSurface or it cannot
283 * read data from the given _aSurface_.
286 static already_AddRefed<DataSourceSurface> ScaleDataSourceSurface(
287 DataSourceSurface* aSurface, const ImageBitmapOptions& aOptions) {
288 MOZ_ASSERT(aSurface);
290 const SurfaceFormat format = aSurface->GetFormat();
291 const int bytesPerPixel = BytesPerPixel(format);
293 const IntSize srcSize = aSurface->GetSize();
294 int32_t tmp;
296 CheckedInt<int32_t> checked;
297 CheckedInt<int32_t> dstWidth(
298 aOptions.mResizeWidth.WasPassed() ? aOptions.mResizeWidth.Value() : 0);
299 CheckedInt<int32_t> dstHeight(
300 aOptions.mResizeHeight.WasPassed() ? aOptions.mResizeHeight.Value() : 0);
302 if (!dstWidth.isValid() || !dstHeight.isValid()) {
303 return nullptr;
306 if (!dstWidth.value()) {
307 checked = srcSize.width * dstHeight;
308 if (!checked.isValid()) {
309 return nullptr;
312 tmp = ceil(checked.value() / double(srcSize.height));
313 dstWidth = tmp;
314 } else if (!dstHeight.value()) {
315 checked = srcSize.height * dstWidth;
316 if (!checked.isValid()) {
317 return nullptr;
320 tmp = ceil(checked.value() / double(srcSize.width));
321 dstHeight = tmp;
324 const IntSize dstSize(dstWidth.value(), dstHeight.value());
325 const int32_t dstStride = dstSize.width * bytesPerPixel;
327 // Create a new SourceSurface.
328 RefPtr<DataSourceSurface> dstDataSurface =
329 Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride,
330 true);
332 if (NS_WARN_IF(!dstDataSurface)) {
333 return nullptr;
336 // Copy the raw data into the newly created DataSourceSurface.
337 DataSourceSurface::ScopedMap srcMap(aSurface, DataSourceSurface::READ);
338 DataSourceSurface::ScopedMap dstMap(dstDataSurface, DataSourceSurface::WRITE);
339 if (NS_WARN_IF(!srcMap.IsMapped()) || NS_WARN_IF(!dstMap.IsMapped())) {
340 return nullptr;
343 uint8_t* srcBufferPtr = srcMap.GetData();
344 uint8_t* dstBufferPtr = dstMap.GetData();
346 bool res = Scale(srcBufferPtr, srcSize.width, srcSize.height,
347 srcMap.GetStride(), dstBufferPtr, dstSize.width,
348 dstSize.height, dstMap.GetStride(), aSurface->GetFormat());
349 if (!res) {
350 return nullptr;
353 return dstDataSurface.forget();
356 static DataSourceSurface* FlipYDataSourceSurface(DataSourceSurface* aSurface) {
357 MOZ_ASSERT(aSurface);
359 // Invert in y direction.
360 DataSourceSurface::ScopedMap srcMap(aSurface, DataSourceSurface::READ_WRITE);
361 if (NS_WARN_IF(!srcMap.IsMapped())) {
362 return nullptr;
365 const IntSize srcSize = aSurface->GetSize();
366 uint8_t* srcBufferPtr = srcMap.GetData();
367 const uint32_t stride = srcMap.GetStride();
369 CheckedInt<uint32_t> copiedBytesPerRaw = CheckedInt<uint32_t>(stride);
370 if (!copiedBytesPerRaw.isValid()) {
371 return nullptr;
374 for (int i = 0; i < srcSize.height / 2; ++i) {
375 std::swap_ranges(srcBufferPtr + stride * i, srcBufferPtr + stride * (i + 1),
376 srcBufferPtr + stride * (srcSize.height - 1 - i));
379 return aSurface;
382 static DataSourceSurface* AlphaPremultiplyDataSourceSurface(
383 DataSourceSurface* aSurface, const bool forward = true) {
384 MOZ_ASSERT(aSurface);
386 DataSourceSurface::MappedSurface surfaceMap;
388 if (aSurface->Map(DataSourceSurface::MapType::READ_WRITE, &surfaceMap)) {
389 if (forward) {
390 PremultiplyData(surfaceMap.mData, surfaceMap.mStride,
391 aSurface->GetFormat(), surfaceMap.mData,
392 surfaceMap.mStride, aSurface->GetFormat(),
393 aSurface->GetSize());
394 } else {
395 UnpremultiplyData(surfaceMap.mData, surfaceMap.mStride,
396 aSurface->GetFormat(), surfaceMap.mData,
397 surfaceMap.mStride, aSurface->GetFormat(),
398 aSurface->GetSize());
401 aSurface->Unmap();
402 } else {
403 return nullptr;
406 return aSurface;
410 * Encapsulate the given _aSurface_ into a layers::SourceSurfaceImage.
412 static already_AddRefed<layers::Image> CreateImageFromSurface(
413 SourceSurface* aSurface) {
414 MOZ_ASSERT(aSurface);
415 RefPtr<layers::SourceSurfaceImage> image =
416 new layers::SourceSurfaceImage(aSurface->GetSize(), aSurface);
417 return image.forget();
421 * CreateImageFromRawData(), CreateSurfaceFromRawData() and
422 * CreateImageFromRawDataInMainThreadSyncTask are helpers for
423 * create-from-ImageData case
425 static already_AddRefed<SourceSurface> CreateSurfaceFromRawData(
426 const gfx::IntSize& aSize, uint32_t aStride, gfx::SurfaceFormat aFormat,
427 uint8_t* aBuffer, uint32_t aBufferLength, const Maybe<IntRect>& aCropRect,
428 const ImageBitmapOptions& aOptions) {
429 MOZ_ASSERT(!aSize.IsEmpty());
430 MOZ_ASSERT(aBuffer);
432 // Wrap the source buffer into a SourceSurface.
433 RefPtr<DataSourceSurface> dataSurface =
434 Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize,
435 aFormat);
437 if (NS_WARN_IF(!dataSurface)) {
438 return nullptr;
441 // The temporary cropRect variable is equal to the size of source buffer if we
442 // do not need to crop, or it equals to the given cropping size.
443 const IntRect cropRect =
444 aCropRect.valueOr(IntRect(0, 0, aSize.width, aSize.height));
446 // Copy the source buffer in the _cropRect_ area into a new SourceSurface.
447 RefPtr<DataSourceSurface> result =
448 CropAndCopyDataSourceSurface(dataSurface, cropRect);
449 if (NS_WARN_IF(!result)) {
450 return nullptr;
453 if (aOptions.mImageOrientation == ImageOrientation::FlipY) {
454 result = FlipYDataSourceSurface(result);
456 if (NS_WARN_IF(!result)) {
457 return nullptr;
461 if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) {
462 result = AlphaPremultiplyDataSourceSurface(result);
464 if (NS_WARN_IF(!result)) {
465 return nullptr;
469 if (aOptions.mResizeWidth.WasPassed() || aOptions.mResizeHeight.WasPassed()) {
470 dataSurface = result->GetDataSurface();
471 result = ScaleDataSourceSurface(dataSurface, aOptions);
472 if (NS_WARN_IF(!result)) {
473 return nullptr;
477 return result.forget();
480 static already_AddRefed<layers::Image> CreateImageFromRawData(
481 const gfx::IntSize& aSize, uint32_t aStride, gfx::SurfaceFormat aFormat,
482 uint8_t* aBuffer, uint32_t aBufferLength, const Maybe<IntRect>& aCropRect,
483 const ImageBitmapOptions& aOptions) {
484 MOZ_ASSERT(NS_IsMainThread());
486 // Copy and crop the source buffer into a SourceSurface.
487 RefPtr<SourceSurface> rgbaSurface = CreateSurfaceFromRawData(
488 aSize, aStride, aFormat, aBuffer, aBufferLength, aCropRect, aOptions);
490 if (NS_WARN_IF(!rgbaSurface)) {
491 return nullptr;
494 // Convert RGBA to BGRA
495 RefPtr<DataSourceSurface> rgbaDataSurface = rgbaSurface->GetDataSurface();
496 DataSourceSurface::ScopedMap rgbaMap(rgbaDataSurface,
497 DataSourceSurface::READ);
498 if (NS_WARN_IF(!rgbaMap.IsMapped())) {
499 return nullptr;
502 RefPtr<DataSourceSurface> bgraDataSurface =
503 Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(),
504 SurfaceFormat::B8G8R8A8,
505 rgbaMap.GetStride());
506 if (NS_WARN_IF(!bgraDataSurface)) {
507 return nullptr;
510 DataSourceSurface::ScopedMap bgraMap(bgraDataSurface,
511 DataSourceSurface::WRITE);
512 if (NS_WARN_IF(!bgraMap.IsMapped())) {
513 return nullptr;
516 SwizzleData(rgbaMap.GetData(), rgbaMap.GetStride(), SurfaceFormat::R8G8B8A8,
517 bgraMap.GetData(), bgraMap.GetStride(), SurfaceFormat::B8G8R8A8,
518 bgraDataSurface->GetSize());
520 // Create an Image from the BGRA SourceSurface.
521 return CreateImageFromSurface(bgraDataSurface);
525 * This is a synchronous task.
526 * This class is used to create a layers::SourceSurfaceImage from raw data in
527 * the main thread. While creating an ImageBitmap from an ImageData, we need to
528 * create a SouceSurface from the ImageData's raw data and then set the
529 * SourceSurface into a layers::SourceSurfaceImage. However, the
530 * layers::SourceSurfaceImage asserts the setting operation in the main thread,
531 * so if we are going to create an ImageBitmap from an ImageData off the main
532 * thread, we post an event to the main thread to create a
533 * layers::SourceSurfaceImage from an ImageData's raw data.
535 class CreateImageFromRawDataInMainThreadSyncTask final
536 : public WorkerMainThreadRunnable {
537 public:
538 CreateImageFromRawDataInMainThreadSyncTask(
539 uint8_t* aBuffer, uint32_t aBufferLength, uint32_t aStride,
540 gfx::SurfaceFormat aFormat, const gfx::IntSize& aSize,
541 const Maybe<IntRect>& aCropRect, layers::Image** aImage,
542 const ImageBitmapOptions& aOptions)
543 : WorkerMainThreadRunnable(
544 GetCurrentThreadWorkerPrivate(),
545 "ImageBitmap :: Create Image from Raw Data"_ns),
546 mImage(aImage),
547 mBuffer(aBuffer),
548 mBufferLength(aBufferLength),
549 mStride(aStride),
550 mFormat(aFormat),
551 mSize(aSize),
552 mCropRect(aCropRect),
553 mOptions(aOptions) {
554 MOZ_ASSERT(!(*aImage),
555 "Don't pass an existing Image into "
556 "CreateImageFromRawDataInMainThreadSyncTask.");
559 bool MainThreadRun() override {
560 RefPtr<layers::Image> image = CreateImageFromRawData(
561 mSize, mStride, mFormat, mBuffer, mBufferLength, mCropRect, mOptions);
563 if (NS_WARN_IF(!image)) {
564 return false;
567 image.forget(mImage);
569 return true;
572 private:
573 layers::Image** mImage;
574 uint8_t* mBuffer;
575 uint32_t mBufferLength;
576 uint32_t mStride;
577 gfx::SurfaceFormat mFormat;
578 gfx::IntSize mSize;
579 const Maybe<IntRect>& mCropRect;
580 const ImageBitmapOptions mOptions;
584 * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the
585 * security checking.
587 template <class ElementType>
588 static already_AddRefed<SourceSurface> GetSurfaceFromElement(
589 nsIGlobalObject* aGlobal, ElementType& aElement, bool* aWriteOnly,
590 const ImageBitmapOptions& aOptions, gfxAlphaType* aAlphaType,
591 ErrorResult& aRv) {
592 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
593 nsLayoutUtils::SFE_ORIENTATION_FROM_IMAGE |
594 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE;
596 // by default surfaces have premultiplied alpha
597 // attempt to get non premultiplied if required
598 if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
599 flags |= nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
602 if (aOptions.mColorSpaceConversion == ColorSpaceConversion::None &&
603 aElement.IsHTMLElement(nsGkAtoms::img)) {
604 flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION;
607 Maybe<int32_t> resizeWidth, resizeHeight;
608 if (aOptions.mResizeWidth.WasPassed()) {
609 if (!CheckedInt32(aOptions.mResizeWidth.Value()).isValid()) {
610 aRv.ThrowInvalidStateError("resizeWidth is too large");
611 return nullptr;
613 resizeWidth.emplace(aOptions.mResizeWidth.Value());
615 if (aOptions.mResizeHeight.WasPassed()) {
616 if (!CheckedInt32(aOptions.mResizeHeight.Value()).isValid()) {
617 aRv.ThrowInvalidStateError("resizeHeight is too large");
618 return nullptr;
620 resizeHeight.emplace(aOptions.mResizeHeight.Value());
622 SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromElement(
623 &aElement, resizeWidth, resizeHeight, flags);
625 RefPtr<SourceSurface> surface = res.GetSourceSurface();
626 if (NS_WARN_IF(!surface)) {
627 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
628 return nullptr;
631 *aWriteOnly = res.mIsWriteOnly;
632 *aAlphaType = res.mAlphaType;
634 return surface.forget();
637 ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData,
638 bool aWriteOnly, gfxAlphaType aAlphaType)
639 : mParent(aGlobal),
640 mData(aData),
641 mSurface(nullptr),
642 mPictureRect(aData->GetPictureRect()),
643 mAlphaType(aAlphaType),
644 mAllocatedImageData(false),
645 mWriteOnly(aWriteOnly) {
646 MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor.");
648 StaticMutexAutoLock lock(sShutdownMutex);
649 if (!sShutdownObserver &&
650 !AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdown)) {
651 sShutdownObserver = new ImageBitmapShutdownObserver();
653 if (sShutdownObserver) {
654 mShutdownRunnable = sShutdownObserver->Track(this);
658 ImageBitmap::~ImageBitmap() {
659 StaticMutexAutoLock lock(sShutdownMutex);
660 if (mShutdownRunnable) {
661 mShutdownRunnable->mImageBitmap = nullptr;
663 mShutdownRunnable = nullptr;
664 if (sShutdownObserver) {
665 sShutdownObserver->Untrack(this);
669 JSObject* ImageBitmap::WrapObject(JSContext* aCx,
670 JS::Handle<JSObject*> aGivenProto) {
671 return ImageBitmap_Binding::Wrap(aCx, this, aGivenProto);
674 void ImageBitmap::Close() {
675 mData = nullptr;
676 mSurface = nullptr;
677 mPictureRect.SetEmpty();
680 void ImageBitmap::OnShutdown() { Close(); }
682 void ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv) {
683 mPictureRect = FixUpNegativeDimension(aRect, aRv);
684 mSurface = nullptr;
687 SurfaceFromElementResult ImageBitmap::SurfaceFrom(uint32_t aSurfaceFlags) {
688 SurfaceFromElementResult sfer;
690 if (!mData) {
691 return sfer;
694 // An ImageBitmap, not being a DOM element, only has `origin-clean`
695 // (via our `IsWriteOnly`), and does not participate in CORS.
696 // Right now we mark this by setting mCORSUsed to true.
697 sfer.mCORSUsed = true;
698 sfer.mIsWriteOnly = mWriteOnly;
700 if (mParent) {
701 sfer.mPrincipal = mParent->PrincipalOrNull();
704 IntSize imageSize(mData->GetSize());
705 IntRect imageRect(IntPoint(0, 0), imageSize);
706 bool hasCropRect = mPictureRect.IsEqualEdges(imageRect);
708 bool wantExactSize =
709 bool(aSurfaceFlags & nsLayoutUtils::SFE_EXACT_SIZE_SURFACE);
710 bool allowNonPremult =
711 bool(aSurfaceFlags & nsLayoutUtils::SFE_ALLOW_NON_PREMULT);
712 bool allowUncropped =
713 bool(aSurfaceFlags & nsLayoutUtils::SFE_ALLOW_UNCROPPED_UNSCALED);
714 bool requiresPremult =
715 !allowNonPremult && mAlphaType == gfxAlphaType::NonPremult;
716 bool requiresCrop = !allowUncropped && hasCropRect;
717 if (wantExactSize || requiresPremult || requiresCrop || mSurface) {
718 RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
719 BackendType::SKIA, IntSize(1, 1), SurfaceFormat::B8G8R8A8);
720 sfer.mSourceSurface = PrepareForDrawTarget(dt);
722 if (!sfer.mSourceSurface) {
723 return sfer;
726 MOZ_ASSERT(mSurface);
728 sfer.mSize = sfer.mIntrinsicSize = sfer.mSourceSurface->GetSize();
729 sfer.mHasSize = true;
730 sfer.mAlphaType = IsOpaque(sfer.mSourceSurface->GetFormat())
731 ? gfxAlphaType::Opaque
732 : gfxAlphaType::Premult;
733 return sfer;
736 if (hasCropRect) {
737 IntRect imagePortion = imageRect.Intersect(mPictureRect);
739 // the crop lies entirely outside the surface area, nothing to draw
740 if (imagePortion.IsEmpty()) {
741 return sfer;
744 sfer.mCropRect = Some(imagePortion);
745 sfer.mIntrinsicSize = imagePortion.Size();
746 } else {
747 sfer.mIntrinsicSize = imageSize;
750 sfer.mSize = imageSize;
751 sfer.mHasSize = true;
752 sfer.mAlphaType = mAlphaType;
753 sfer.mLayersImage = mData;
754 return sfer;
758 * The functionality of PrepareForDrawTarget method:
759 * (1) Get a SourceSurface from the mData (which is a layers::Image).
760 * (2) Convert the SourceSurface to format B8G8R8A8 if the original format is
761 * R8G8B8, B8G8R8, HSV or Lab.
762 * Note: if the original format is A8 or Depth, then return null directly.
763 * (3) Do cropping if the size of SourceSurface does not equal to the
764 * mPictureRect.
765 * (4) Pre-multiply alpha if needed.
767 already_AddRefed<SourceSurface> ImageBitmap::PrepareForDrawTarget(
768 gfx::DrawTarget* aTarget) {
769 MOZ_ASSERT(aTarget);
771 if (!mData) {
772 return nullptr;
775 // We may have a cached surface optimized for the last DrawTarget. If it was
776 // created via DrawTargetRecording, it may not be suitable for use with the
777 // current DrawTarget, as we can only do readbacks via the
778 // PersistentBufferProvider for the canvas, and not for individual
779 // SourceSurfaceRecording objects. In such situations, the only thing we can
780 // do is clear our cache and extract a new SourceSurface from mData.
781 if (mSurface && mSurface->GetType() == gfx::SurfaceType::RECORDING &&
782 !aTarget->IsRecording()) {
783 RefPtr<gfx::DataSourceSurface> dataSurface = mSurface->GetDataSurface();
784 if (!dataSurface) {
785 mSurface = nullptr;
789 // If we have a surface, then it is already cropped and premultiplied.
790 if (mSurface) {
791 return do_AddRef(mSurface);
794 RefPtr<gfx::SourceSurface> surface = mData->GetAsSourceSurface();
795 if (NS_WARN_IF(!surface)) {
796 return nullptr;
799 IntRect surfRect(0, 0, surface->GetSize().width, surface->GetSize().height);
800 SurfaceFormat format = surface->GetFormat();
801 bool isOpaque = IsOpaque(format);
802 bool mustPremultiply = mAlphaType == gfxAlphaType::NonPremult && !isOpaque;
803 bool hasCopied = false;
805 // Check if we still need to crop our surface
806 if (!mPictureRect.IsEqualEdges(surfRect)) {
807 IntRect surfPortion = surfRect.Intersect(mPictureRect);
809 // the crop lies entirely outside the surface area, nothing to draw
810 if (surfPortion.IsEmpty()) {
811 return nullptr;
814 IntPoint dest(std::max(0, surfPortion.X() - mPictureRect.X()),
815 std::max(0, surfPortion.Y() - mPictureRect.Y()));
817 // We must initialize this target with mPictureRect.Size() because the
818 // specification states that if the cropping area is given, then return an
819 // ImageBitmap with the size equals to the cropping area. Ensure that the
820 // format matches the surface, even though the DT type is similar to the
821 // destination, i.e. blending an alpha surface to an opaque DT. However,
822 // any pixels outside the surface portion must be filled with transparent
823 // black, even if the surface is opaque, so force to an alpha format in
824 // that case.
825 if (!surfPortion.IsEqualEdges(mPictureRect) && isOpaque) {
826 format = SurfaceFormat::B8G8R8A8;
829 // If we need to pre-multiply the alpha, then we need to be able to
830 // read/write the pixel data, and as such, we want to avoid creating a
831 // SourceSurfaceRecording for the same reasons earlier in this method.
832 RefPtr<DrawTarget> cropped;
833 if (mustPremultiply && aTarget->IsRecording()) {
834 cropped = Factory::CreateDrawTarget(BackendType::SKIA,
835 mPictureRect.Size(), format);
836 } else {
837 cropped = aTarget->CreateSimilarDrawTarget(mPictureRect.Size(), format);
840 if (NS_WARN_IF(!cropped)) {
841 return nullptr;
844 cropped->CopySurface(surface, surfPortion, dest);
845 surface = cropped->GetBackingSurface();
846 hasCopied = true;
847 if (NS_WARN_IF(!surface)) {
848 return nullptr;
852 // Pre-multiply alpha here.
853 // Ignore this step if the source surface does not have alpha channel; this
854 // kind of source surfaces might come form layers::PlanarYCbCrImage. If the
855 // crop rect imputed transparency, and the original surface was opaque, we
856 // can skip doing the pre-multiply here as the only transparent pixels are
857 // already transparent black.
858 if (mustPremultiply) {
859 MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::R8G8B8A8 ||
860 surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
861 surface->GetFormat() == SurfaceFormat::A8R8G8B8);
863 RefPtr<DataSourceSurface> srcSurface = surface->GetDataSurface();
864 if (NS_WARN_IF(!srcSurface)) {
865 return nullptr;
868 if (hasCopied) {
869 // If we are using our own local copy, then we can safely premultiply in
870 // place without an additional allocation.
871 DataSourceSurface::ScopedMap map(srcSurface,
872 DataSourceSurface::READ_WRITE);
873 if (!map.IsMapped()) {
874 gfxCriticalError() << "Failed to map surface for premultiplying alpha.";
875 return nullptr;
878 PremultiplyData(map.GetData(), map.GetStride(), srcSurface->GetFormat(),
879 map.GetData(), map.GetStride(), srcSurface->GetFormat(),
880 surface->GetSize());
881 } else {
882 RefPtr<DataSourceSurface> dstSurface = Factory::CreateDataSourceSurface(
883 srcSurface->GetSize(), srcSurface->GetFormat());
884 if (NS_WARN_IF(!dstSurface)) {
885 return nullptr;
888 DataSourceSurface::ScopedMap srcMap(srcSurface, DataSourceSurface::READ);
889 if (!srcMap.IsMapped()) {
890 gfxCriticalError()
891 << "Failed to map source surface for premultiplying alpha.";
892 return nullptr;
895 DataSourceSurface::ScopedMap dstMap(dstSurface, DataSourceSurface::WRITE);
896 if (!dstMap.IsMapped()) {
897 gfxCriticalError()
898 << "Failed to map destination surface for premultiplying alpha.";
899 return nullptr;
902 PremultiplyData(srcMap.GetData(), srcMap.GetStride(),
903 srcSurface->GetFormat(), dstMap.GetData(),
904 dstMap.GetStride(), dstSurface->GetFormat(),
905 dstSurface->GetSize());
907 surface = std::move(dstSurface);
911 // Replace our surface with one optimized for the target we're about to draw
912 // to, under the assumption it'll likely be drawn again to that target.
913 // This call should be a no-op for already-optimized surfaces
914 mSurface = aTarget->OptimizeSourceSurface(surface);
915 if (!mSurface) {
916 mSurface = std::move(surface);
918 return do_AddRef(mSurface);
921 already_AddRefed<layers::Image> ImageBitmap::TransferAsImage() {
922 RefPtr<layers::Image> image = mData;
923 Close();
924 return image.forget();
927 UniquePtr<ImageBitmapCloneData> ImageBitmap::ToCloneData() const {
928 if (!mData) {
929 // A closed image cannot be cloned.
930 return nullptr;
933 UniquePtr<ImageBitmapCloneData> result(new ImageBitmapCloneData());
934 result->mPictureRect = mPictureRect;
935 result->mAlphaType = mAlphaType;
936 RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
937 if (!surface) {
938 // It might just not be possible to get/map the surface. (e.g. from another
939 // process)
940 return nullptr;
943 result->mSurface = surface->GetDataSurface();
944 MOZ_ASSERT(result->mSurface);
945 result->mWriteOnly = mWriteOnly;
947 return result;
950 /* static */
951 already_AddRefed<ImageBitmap> ImageBitmap::CreateFromSourceSurface(
952 nsIGlobalObject* aGlobal, gfx::SourceSurface* aSource, ErrorResult& aRv) {
953 RefPtr<layers::Image> data = CreateImageFromSurface(aSource);
954 RefPtr<ImageBitmap> ret =
955 new ImageBitmap(aGlobal, data, false /* writeOnly */);
956 ret->mAllocatedImageData = true;
957 return ret.forget();
960 /* static */
961 already_AddRefed<ImageBitmap> ImageBitmap::CreateFromCloneData(
962 nsIGlobalObject* aGlobal, ImageBitmapCloneData* aData) {
963 RefPtr<layers::Image> data = CreateImageFromSurface(aData->mSurface);
965 RefPtr<ImageBitmap> ret =
966 new ImageBitmap(aGlobal, data, aData->mWriteOnly, aData->mAlphaType);
968 ret->mAllocatedImageData = true;
970 ErrorResult rv;
971 ret->SetPictureRect(aData->mPictureRect, rv);
972 return ret.forget();
975 /* static */
976 already_AddRefed<ImageBitmap> ImageBitmap::CreateFromOffscreenCanvas(
977 nsIGlobalObject* aGlobal, OffscreenCanvas& aOffscreenCanvas,
978 ErrorResult& aRv) {
979 // Check write-only mode.
980 bool writeOnly = aOffscreenCanvas.IsWriteOnly();
981 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
982 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE;
984 SurfaceFromElementResult res =
985 nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas, flags);
987 RefPtr<SourceSurface> surface = res.GetSourceSurface();
989 if (NS_WARN_IF(!surface)) {
990 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
991 return nullptr;
994 RefPtr<layers::Image> data = CreateImageFromSurface(surface);
996 RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
998 ret->mAllocatedImageData = true;
1000 return ret.forget();
1003 /* static */
1004 already_AddRefed<ImageBitmap> ImageBitmap::CreateImageBitmapInternal(
1005 nsIGlobalObject* aGlobal, gfx::SourceSurface* aSurface,
1006 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1007 const bool aWriteOnly, const bool aAllocatedImageData, const bool aMustCopy,
1008 const gfxAlphaType aAlphaType, ErrorResult& aRv) {
1009 bool needToReportMemoryAllocation = aAllocatedImageData;
1010 const IntSize srcSize = aSurface->GetSize();
1011 IntRect cropRect =
1012 aCropRect.valueOr(IntRect(0, 0, srcSize.width, srcSize.height));
1014 RefPtr<SourceSurface> surface = aSurface;
1015 RefPtr<DataSourceSurface> dataSurface;
1017 // handle alpha premultiplication if surface not of correct type
1019 gfxAlphaType alphaType = aAlphaType;
1020 bool mustCopy = aMustCopy;
1021 bool requiresPremultiply = false;
1022 bool requiresUnpremultiply = false;
1024 if (!IsOpaque(surface->GetFormat())) {
1025 if (aAlphaType == gfxAlphaType::Premult &&
1026 aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
1027 requiresUnpremultiply = true;
1028 alphaType = gfxAlphaType::NonPremult;
1029 if (!aAllocatedImageData) {
1030 mustCopy = true;
1032 } else if (aAlphaType == gfxAlphaType::NonPremult &&
1033 aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply) {
1034 requiresPremultiply = true;
1035 alphaType = gfxAlphaType::Premult;
1036 if (!aAllocatedImageData) {
1037 mustCopy = true;
1043 * if we don't own the data and need to create a new buffer to flip Y.
1044 * or
1045 * we need to crop and flip, where crop must come first.
1046 * or
1047 * Or the caller demands a copy (WebGL contexts).
1049 if ((aOptions.mImageOrientation == ImageOrientation::FlipY &&
1050 (!aAllocatedImageData || aCropRect.isSome())) ||
1051 mustCopy) {
1052 dataSurface = surface->GetDataSurface();
1054 dataSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
1055 if (NS_WARN_IF(!dataSurface)) {
1056 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1057 return nullptr;
1060 surface = dataSurface;
1061 cropRect.SetRect(0, 0, dataSurface->GetSize().width,
1062 dataSurface->GetSize().height);
1063 needToReportMemoryAllocation = true;
1066 // flip image in Y direction
1067 if (aOptions.mImageOrientation == ImageOrientation::FlipY) {
1068 if (!dataSurface) {
1069 dataSurface = surface->GetDataSurface();
1072 surface = FlipYDataSourceSurface(dataSurface);
1073 if (NS_WARN_IF(!surface)) {
1074 return nullptr;
1078 if (requiresPremultiply) {
1079 if (!dataSurface) {
1080 dataSurface = surface->GetDataSurface();
1083 surface = AlphaPremultiplyDataSourceSurface(dataSurface, true);
1084 if (NS_WARN_IF(!surface)) {
1085 return nullptr;
1089 // resize if required
1090 if (aOptions.mResizeWidth.WasPassed() || aOptions.mResizeHeight.WasPassed()) {
1091 if (!dataSurface) {
1092 dataSurface = surface->GetDataSurface();
1095 surface = ScaleDataSourceSurface(dataSurface, aOptions);
1096 if (NS_WARN_IF(!surface)) {
1097 aRv.ThrowInvalidStateError("Failed to create resized image");
1098 return nullptr;
1101 needToReportMemoryAllocation = true;
1102 cropRect.SetRect(0, 0, surface->GetSize().width, surface->GetSize().height);
1105 if (requiresUnpremultiply) {
1106 if (!dataSurface) {
1107 dataSurface = surface->GetDataSurface();
1110 surface = AlphaPremultiplyDataSourceSurface(dataSurface, false);
1111 if (NS_WARN_IF(!surface)) {
1112 return nullptr;
1116 // Create an Image from the SourceSurface.
1117 RefPtr<layers::Image> data = CreateImageFromSurface(surface);
1118 RefPtr<ImageBitmap> ret =
1119 new ImageBitmap(aGlobal, data, aWriteOnly, alphaType);
1121 if (needToReportMemoryAllocation) {
1122 ret->mAllocatedImageData = true;
1125 // Set the picture rectangle.
1126 ret->SetPictureRect(cropRect, aRv);
1128 return ret.forget();
1131 /* static */
1132 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1133 nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl,
1134 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1135 ErrorResult& aRv) {
1136 // Check if the image element is completely available or not.
1137 if (!aImageEl.Complete()) {
1138 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1139 return nullptr;
1142 bool writeOnly = true;
1143 gfxAlphaType alphaType = gfxAlphaType::NonPremult;
1145 // Get the SourceSurface out from the image element and then do security
1146 // checking.
1147 RefPtr<SourceSurface> surface = GetSurfaceFromElement(
1148 aGlobal, aImageEl, &writeOnly, aOptions, &alphaType, aRv);
1150 if (NS_WARN_IF(aRv.Failed())) {
1151 return nullptr;
1154 bool needToReportMemoryAllocation = false;
1155 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1156 writeOnly, needToReportMemoryAllocation,
1157 false, alphaType, aRv);
1159 /* static */
1160 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1161 nsIGlobalObject* aGlobal, SVGImageElement& aImageEl,
1162 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1163 ErrorResult& aRv) {
1164 bool writeOnly = true;
1165 gfxAlphaType alphaType = gfxAlphaType::NonPremult;
1167 // Get the SourceSurface out from the image element and then do security
1168 // checking.
1169 RefPtr<SourceSurface> surface = GetSurfaceFromElement(
1170 aGlobal, aImageEl, &writeOnly, aOptions, &alphaType, aRv);
1172 if (NS_WARN_IF(aRv.Failed())) {
1173 return nullptr;
1176 bool needToReportMemoryAllocation = false;
1178 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1179 writeOnly, needToReportMemoryAllocation,
1180 false, alphaType, aRv);
1183 /* static */
1184 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1185 nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl,
1186 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1187 ErrorResult& aRv) {
1188 aVideoEl.LogVisibility(
1189 mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_IMAGEBITMAP);
1191 // Check network state.
1192 if (aVideoEl.NetworkState() == NETWORK_EMPTY) {
1193 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1194 return nullptr;
1197 // Check ready state.
1198 // Cannot be HTMLMediaElement::HAVE_NOTHING or
1199 // HTMLMediaElement::HAVE_METADATA.
1200 if (aVideoEl.ReadyState() <= HAVE_METADATA) {
1201 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1202 return nullptr;
1205 // Check security.
1206 nsCOMPtr<nsIPrincipal> principal = aVideoEl.GetCurrentVideoPrincipal();
1207 bool hadCrossOriginRedirects = aVideoEl.HadCrossOriginRedirects();
1208 bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE;
1209 bool writeOnly = CanvasUtils::CheckWriteOnlySecurity(CORSUsed, principal,
1210 hadCrossOriginRedirects);
1212 // Create ImageBitmap.
1213 RefPtr<layers::Image> data = aVideoEl.GetCurrentImage();
1214 if (!data) {
1215 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1216 return nullptr;
1219 RefPtr<SourceSurface> surface = data->GetAsSourceSurface();
1220 if (!surface) {
1221 // preserve original behavior in case of unavailble surface
1222 RefPtr<ImageBitmap> ret = new ImageBitmap(aGlobal, data, writeOnly);
1223 return ret.forget();
1226 bool needToReportMemoryAllocation = false;
1228 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1229 writeOnly, needToReportMemoryAllocation,
1230 false, gfxAlphaType::Premult, aRv);
1233 /* static */
1234 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1235 nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl,
1236 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1237 ErrorResult& aRv) {
1238 if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) {
1239 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1240 return nullptr;
1243 bool writeOnly = true;
1244 gfxAlphaType alphaType = gfxAlphaType::NonPremult;
1246 RefPtr<SourceSurface> surface = GetSurfaceFromElement(
1247 aGlobal, aCanvasEl, &writeOnly, aOptions, &alphaType, aRv);
1249 if (NS_WARN_IF(aRv.Failed())) {
1250 return nullptr;
1253 if (!writeOnly) {
1254 writeOnly = aCanvasEl.IsWriteOnly();
1257 // If the HTMLCanvasElement's rendering context is WebGL/WebGPU,
1258 // then the snapshot we got from the HTMLCanvasElement is
1259 // a DataSourceSurface which is a copy of the rendering context.
1260 // We handle cropping in this case.
1261 bool needToReportMemoryAllocation = false;
1262 bool mustCopy = false;
1264 if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 ||
1265 aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2 ||
1266 aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGPU) &&
1267 aCropRect.isSome()) {
1268 mustCopy = true;
1271 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1272 writeOnly, needToReportMemoryAllocation,
1273 mustCopy, alphaType, aRv);
1276 /* static */
1277 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1278 nsIGlobalObject* aGlobal, OffscreenCanvas& aOffscreenCanvas,
1279 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1280 ErrorResult& aRv) {
1281 if (aOffscreenCanvas.Width() == 0) {
1282 aRv.ThrowInvalidStateError("Passed-in canvas has width 0");
1283 return nullptr;
1286 if (aOffscreenCanvas.Height() == 0) {
1287 aRv.ThrowInvalidStateError("Passed-in canvas has height 0");
1288 return nullptr;
1291 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE |
1292 nsLayoutUtils::SFE_EXACT_SIZE_SURFACE;
1294 // by default surfaces have premultiplied alpha
1295 // attempt to get non premultiplied if required
1296 if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
1297 flags |= nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
1300 SurfaceFromElementResult res =
1301 nsLayoutUtils::SurfaceFromOffscreenCanvas(&aOffscreenCanvas, flags);
1303 RefPtr<SourceSurface> surface = res.GetSourceSurface();
1304 if (NS_WARN_IF(!surface)) {
1305 aRv.ThrowInvalidStateError("Passed-in canvas failed to create snapshot");
1306 return nullptr;
1309 gfxAlphaType alphaType = res.mAlphaType;
1310 bool writeOnly = res.mIsWriteOnly;
1312 // If the OffscreenCanvas's rendering context is WebGL/WebGPU, then the
1313 // snapshot we got from the OffscreenCanvas is a DataSourceSurface which
1314 // is a copy of the rendering context. We handle cropping in this case.
1315 bool needToReportMemoryAllocation = false;
1316 bool mustCopy =
1317 aCropRect.isSome() &&
1318 (aOffscreenCanvas.GetContextType() == CanvasContextType::WebGL1 ||
1319 aOffscreenCanvas.GetContextType() == CanvasContextType::WebGL2 ||
1320 aOffscreenCanvas.GetContextType() == CanvasContextType::WebGPU);
1322 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1323 writeOnly, needToReportMemoryAllocation,
1324 mustCopy, alphaType, aRv);
1327 /* static */
1328 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1329 nsIGlobalObject* aGlobal, ImageData& aImageData,
1330 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1331 ErrorResult& aRv) {
1332 // Copy data into SourceSurface.
1333 RootedSpiderMonkeyInterface<Uint8ClampedArray> array(RootingCx());
1334 if (!array.Init(aImageData.GetDataObject())) {
1335 aRv.ThrowInvalidStateError(
1336 "Failed to extract Uint8ClampedArray from ImageData (security check "
1337 "failed?)");
1338 return nullptr;
1340 const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8;
1341 // ImageData's underlying data is not alpha-premultiplied.
1342 auto alphaType = (aOptions.mPremultiplyAlpha == PremultiplyAlpha::Premultiply)
1343 ? gfxAlphaType::Premult
1344 : gfxAlphaType::NonPremult;
1346 const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT);
1347 const uint32_t imageWidth = aImageData.Width();
1348 const uint32_t imageHeight = aImageData.Height();
1349 const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL;
1350 const gfx::IntSize imageSize(imageWidth, imageHeight);
1352 // Check the ImageData is neutered or not.
1353 if (imageWidth == 0 || imageHeight == 0) {
1354 aRv.ThrowInvalidStateError("Passed-in image is empty");
1355 return nullptr;
1358 return array.ProcessFixedData(
1359 [&](const Span<const uint8_t>& aData) -> already_AddRefed<ImageBitmap> {
1360 const uint32_t dataLength = aData.Length();
1361 if ((imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) {
1362 aRv.ThrowInvalidStateError("Data size / image format mismatch");
1363 return nullptr;
1366 // Create and Crop the raw data into a layers::Image
1367 RefPtr<layers::Image> data;
1369 uint8_t* fixedData = const_cast<uint8_t*>(aData.Elements());
1371 if (NS_IsMainThread()) {
1372 data =
1373 CreateImageFromRawData(imageSize, imageStride, FORMAT, fixedData,
1374 dataLength, aCropRect, aOptions);
1375 } else {
1376 RefPtr<CreateImageFromRawDataInMainThreadSyncTask> task =
1377 new CreateImageFromRawDataInMainThreadSyncTask(
1378 fixedData, dataLength, imageStride, FORMAT, imageSize,
1379 aCropRect, getter_AddRefs(data), aOptions);
1380 task->Dispatch(Canceling, aRv);
1383 if (NS_WARN_IF(!data)) {
1384 aRv.ThrowInvalidStateError("Failed to create internal image");
1385 return nullptr;
1388 // Create an ImageBitmap.
1389 RefPtr<ImageBitmap> ret =
1390 new ImageBitmap(aGlobal, data, false /* write-only */, alphaType);
1391 ret->mAllocatedImageData = true;
1393 // The cropping information has been handled in the
1394 // CreateImageFromRawData() function.
1396 return ret.forget();
1400 /* static */
1401 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1402 nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx,
1403 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1404 ErrorResult& aRv) {
1405 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
1406 nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(win);
1407 if (NS_WARN_IF(!window) || !window->GetExtantDoc()) {
1408 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1409 return nullptr;
1412 window->GetExtantDoc()->WarnOnceAbout(
1413 DeprecatedOperations::eCreateImageBitmapCanvasRenderingContext2D);
1415 // Check write-only mode.
1416 bool writeOnly =
1417 aCanvasCtx.GetCanvas()->IsWriteOnly() || aCanvasCtx.IsWriteOnly();
1419 RefPtr<SourceSurface> surface = aCanvasCtx.GetSurfaceSnapshot();
1421 if (NS_WARN_IF(!surface)) {
1422 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1423 return nullptr;
1426 const IntSize surfaceSize = surface->GetSize();
1427 if (surfaceSize.width == 0 || surfaceSize.height == 0) {
1428 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1429 return nullptr;
1432 bool needToReportMemoryAllocation = true;
1434 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1435 writeOnly, needToReportMemoryAllocation,
1436 false, gfxAlphaType::Premult, aRv);
1439 /* static */
1440 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1441 nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap,
1442 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1443 ErrorResult& aRv) {
1444 if (!aImageBitmap.mData) {
1445 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1446 return nullptr;
1449 IntRect cropRect;
1450 RefPtr<SourceSurface> surface;
1451 RefPtr<DataSourceSurface> dataSurface;
1452 gfxAlphaType alphaType;
1454 bool needToReportMemoryAllocation = false;
1456 if (aImageBitmap.mSurface &&
1457 (dataSurface = aImageBitmap.mSurface->GetDataSurface())) {
1458 // the source imageBitmap already has a cropped surface, and we can get a
1459 // DataSourceSurface from it, so just use it directly
1460 surface = aImageBitmap.mSurface;
1461 cropRect = aCropRect.valueOr(IntRect(IntPoint(0, 0), surface->GetSize()));
1462 alphaType = IsOpaque(surface->GetFormat()) ? gfxAlphaType::Opaque
1463 : gfxAlphaType::Premult;
1464 } else {
1465 RefPtr<layers::Image> data = aImageBitmap.mData;
1466 surface = data->GetAsSourceSurface();
1467 if (NS_WARN_IF(!surface)) {
1468 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1469 return nullptr;
1472 cropRect = aImageBitmap.mPictureRect;
1473 alphaType = aImageBitmap.mAlphaType;
1474 if (aCropRect.isSome()) {
1475 // get new crop rect relative to original uncropped surface
1476 IntRect newCropRect = aCropRect.ref();
1477 newCropRect = FixUpNegativeDimension(newCropRect, aRv);
1479 newCropRect.MoveBy(cropRect.X(), cropRect.Y());
1481 if (cropRect.Contains(newCropRect)) {
1482 // new crop region within existing surface
1483 // safe to just crop this with new rect
1484 cropRect = newCropRect;
1485 } else {
1486 // crop includes area outside original cropped region
1487 // create new surface cropped by original bitmap crop rect
1488 RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
1490 surface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
1491 if (NS_WARN_IF(!surface)) {
1492 aRv.Throw(NS_ERROR_NOT_AVAILABLE);
1493 return nullptr;
1495 needToReportMemoryAllocation = true;
1496 cropRect = aCropRect.ref();
1501 return CreateImageBitmapInternal(
1502 aGlobal, surface, Some(cropRect), aOptions, aImageBitmap.mWriteOnly,
1503 needToReportMemoryAllocation, false, alphaType, aRv);
1506 /* static */
1507 already_AddRefed<ImageBitmap> ImageBitmap::CreateInternal(
1508 nsIGlobalObject* aGlobal, VideoFrame& aVideoFrame,
1509 const Maybe<IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1510 ErrorResult& aRv) {
1511 if (aVideoFrame.CodedWidth() == 0) {
1512 aRv.ThrowInvalidStateError("Passed-in video frame has width 0");
1513 return nullptr;
1516 if (aVideoFrame.CodedHeight() == 0) {
1517 aRv.ThrowInvalidStateError("Passed-in video frame has height 0");
1518 return nullptr;
1521 uint32_t flags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE;
1523 // by default surfaces have premultiplied alpha
1524 // attempt to get non premultiplied if required
1525 if (aOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
1526 flags |= nsLayoutUtils::SFE_ALLOW_NON_PREMULT;
1529 SurfaceFromElementResult res =
1530 nsLayoutUtils::SurfaceFromVideoFrame(&aVideoFrame, flags);
1532 RefPtr<SourceSurface> surface = res.GetSourceSurface();
1533 if (NS_WARN_IF(!surface)) {
1534 aRv.ThrowInvalidStateError("Passed-in video frame has no surface data");
1535 return nullptr;
1538 gfxAlphaType alphaType = res.mAlphaType;
1539 bool writeOnly = res.mIsWriteOnly;
1540 bool needToReportMemoryAllocation = false;
1541 bool mustCopy = false;
1543 return CreateImageBitmapInternal(aGlobal, surface, aCropRect, aOptions,
1544 writeOnly, needToReportMemoryAllocation,
1545 mustCopy, alphaType, aRv);
1548 class FulfillImageBitmapPromise {
1549 protected:
1550 FulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap)
1551 : mPromise(aPromise), mImageBitmap(aImageBitmap) {
1552 MOZ_ASSERT(aPromise);
1555 void DoFulfillImageBitmapPromise() { mPromise->MaybeResolve(mImageBitmap); }
1557 private:
1558 RefPtr<Promise> mPromise;
1559 RefPtr<ImageBitmap> mImageBitmap;
1562 class FulfillImageBitmapPromiseTask final : public Runnable,
1563 public FulfillImageBitmapPromise {
1564 public:
1565 FulfillImageBitmapPromiseTask(Promise* aPromise, ImageBitmap* aImageBitmap)
1566 : Runnable("dom::FulfillImageBitmapPromiseTask"),
1567 FulfillImageBitmapPromise(aPromise, aImageBitmap) {}
1569 NS_IMETHOD Run() override {
1570 DoFulfillImageBitmapPromise();
1571 return NS_OK;
1575 class FulfillImageBitmapPromiseWorkerTask final
1576 : public WorkerSameThreadRunnable,
1577 public FulfillImageBitmapPromise {
1578 public:
1579 FulfillImageBitmapPromiseWorkerTask(Promise* aPromise,
1580 ImageBitmap* aImageBitmap)
1581 : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()),
1582 FulfillImageBitmapPromise(aPromise, aImageBitmap) {}
1584 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1585 DoFulfillImageBitmapPromise();
1586 return true;
1590 static void AsyncFulfillImageBitmapPromise(Promise* aPromise,
1591 ImageBitmap* aImageBitmap) {
1592 if (NS_IsMainThread()) {
1593 nsCOMPtr<nsIRunnable> task =
1594 new FulfillImageBitmapPromiseTask(aPromise, aImageBitmap);
1595 NS_DispatchToCurrentThread(task); // Actually, to the main-thread.
1596 } else {
1597 RefPtr<FulfillImageBitmapPromiseWorkerTask> task =
1598 new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap);
1599 task->Dispatch(); // Actually, to the current worker-thread.
1603 class CreateImageBitmapFromBlobRunnable;
1605 class CreateImageBitmapFromBlob final : public DiscardableRunnable,
1606 public imgIContainerCallback,
1607 public nsIInputStreamCallback {
1608 friend class CreateImageBitmapFromBlobRunnable;
1610 public:
1611 NS_DECL_ISUPPORTS_INHERITED
1612 NS_DECL_IMGICONTAINERCALLBACK
1613 NS_DECL_NSIINPUTSTREAMCALLBACK
1615 static already_AddRefed<CreateImageBitmapFromBlob> Create(
1616 Promise* aPromise, nsIGlobalObject* aGlobal, Blob& aBlob,
1617 const Maybe<IntRect>& aCropRect, nsIEventTarget* aMainThreadEventTarget,
1618 const ImageBitmapOptions& aOptions);
1620 NS_IMETHOD Run() override {
1621 MOZ_ASSERT(IsCurrentThread());
1623 nsresult rv = StartMimeTypeAndDecodeAndCropBlob();
1624 if (NS_WARN_IF(NS_FAILED(rv))) {
1625 MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
1628 return NS_OK;
1631 // Called by the WorkerRef.
1632 void WorkerShuttingDown();
1634 private:
1635 CreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal,
1636 already_AddRefed<nsIInputStream> aInputStream,
1637 const Maybe<IntRect>& aCropRect,
1638 nsIEventTarget* aMainThreadEventTarget,
1639 const ImageBitmapOptions& aOptions)
1640 : DiscardableRunnable("dom::CreateImageBitmapFromBlob"),
1642 mMutex("dom::CreateImageBitmapFromBlob::mMutex"),
1643 mPromise(aPromise),
1644 mGlobalObject(aGlobal),
1645 mInputStream(std::move(aInputStream)),
1646 mCropRect(aCropRect),
1647 mMainThreadEventTarget(aMainThreadEventTarget),
1648 mOptions(aOptions),
1649 mThread(PR_GetCurrentThread()) {}
1651 virtual ~CreateImageBitmapFromBlob() = default;
1653 bool IsCurrentThread() const { return mThread == PR_GetCurrentThread(); }
1655 // Called on the owning thread.
1656 nsresult StartMimeTypeAndDecodeAndCropBlob();
1658 // Will be called when the decoding + cropping is completed on the
1659 // main-thread. This could the not the owning thread!
1660 void MimeTypeAndDecodeAndCropBlobCompletedMainThread(layers::Image* aImage,
1661 nsresult aStatus);
1663 // Will be called when the decoding + cropping is completed on the owning
1664 // thread.
1665 void MimeTypeAndDecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
1666 nsresult aStatus);
1668 // This is called on the main-thread only.
1669 nsresult MimeTypeAndDecodeAndCropBlob();
1671 // This is called on the main-thread only.
1672 nsresult DecodeAndCropBlob(const nsACString& aMimeType);
1674 // This is called on the main-thread only.
1675 nsresult GetMimeTypeSync(nsACString& aMimeType);
1677 // This is called on the main-thread only.
1678 nsresult GetMimeTypeAsync();
1680 Mutex mMutex MOZ_UNANNOTATED;
1682 // The access to this object is protected by mutex but is always nullified on
1683 // the owning thread.
1684 RefPtr<ThreadSafeWorkerRef> mWorkerRef;
1686 // Touched only on the owning thread.
1687 RefPtr<Promise> mPromise;
1689 // Touched only on the owning thread.
1690 nsCOMPtr<nsIGlobalObject> mGlobalObject;
1692 nsCOMPtr<nsIInputStream> mInputStream;
1693 Maybe<IntRect> mCropRect;
1694 nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
1695 const ImageBitmapOptions mOptions;
1696 void* mThread;
1699 NS_IMPL_ISUPPORTS_INHERITED(CreateImageBitmapFromBlob, DiscardableRunnable,
1700 imgIContainerCallback, nsIInputStreamCallback)
1702 class CreateImageBitmapFromBlobRunnable : public WorkerRunnable {
1703 public:
1704 explicit CreateImageBitmapFromBlobRunnable(WorkerPrivate* aWorkerPrivate,
1705 CreateImageBitmapFromBlob* aTask,
1706 layers::Image* aImage,
1707 nsresult aStatus)
1708 : WorkerRunnable(aWorkerPrivate),
1709 mTask(aTask),
1710 mImage(aImage),
1711 mStatus(aStatus) {}
1713 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
1714 mTask->MimeTypeAndDecodeAndCropBlobCompletedOwningThread(mImage, mStatus);
1715 return true;
1718 private:
1719 RefPtr<CreateImageBitmapFromBlob> mTask;
1720 RefPtr<layers::Image> mImage;
1721 nsresult mStatus;
1724 static void AsyncCreateImageBitmapFromBlob(Promise* aPromise,
1725 nsIGlobalObject* aGlobal,
1726 Blob& aBlob,
1727 const Maybe<IntRect>& aCropRect,
1728 const ImageBitmapOptions& aOptions) {
1729 // Let's identify the main-thread event target.
1730 nsCOMPtr<nsIEventTarget> mainThreadEventTarget;
1731 if (NS_IsMainThread()) {
1732 mainThreadEventTarget = aGlobal->SerialEventTarget();
1733 } else {
1734 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
1735 MOZ_ASSERT(workerPrivate);
1736 mainThreadEventTarget = workerPrivate->MainThreadEventTarget();
1739 RefPtr<CreateImageBitmapFromBlob> task = CreateImageBitmapFromBlob::Create(
1740 aPromise, aGlobal, aBlob, aCropRect, mainThreadEventTarget, aOptions);
1741 if (NS_WARN_IF(!task)) {
1742 aPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
1743 return;
1746 NS_DispatchToCurrentThread(task);
1749 /* static */
1750 already_AddRefed<Promise> ImageBitmap::Create(
1751 nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc,
1752 const Maybe<gfx::IntRect>& aCropRect, const ImageBitmapOptions& aOptions,
1753 ErrorResult& aRv) {
1754 MOZ_ASSERT(aGlobal);
1756 RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
1758 if (NS_WARN_IF(aRv.Failed())) {
1759 return nullptr;
1762 if (aCropRect.isSome()) {
1763 if (aCropRect->Width() == 0) {
1764 aRv.ThrowRangeError(
1765 "The crop rect width passed to createImageBitmap must be nonzero");
1766 return promise.forget();
1769 if (aCropRect->Height() == 0) {
1770 aRv.ThrowRangeError(
1771 "The crop rect height passed to createImageBitmap must be nonzero");
1772 return promise.forget();
1776 if (aOptions.mResizeWidth.WasPassed() && aOptions.mResizeWidth.Value() == 0) {
1777 aRv.ThrowInvalidStateError(
1778 "The resizeWidth passed to createImageBitmap must be nonzero");
1779 return promise.forget();
1782 if (aOptions.mResizeHeight.WasPassed() &&
1783 aOptions.mResizeHeight.Value() == 0) {
1784 aRv.ThrowInvalidStateError(
1785 "The resizeHeight passed to createImageBitmap must be nonzero");
1786 return promise.forget();
1789 RefPtr<ImageBitmap> imageBitmap;
1791 if (aSrc.IsHTMLImageElement()) {
1792 MOZ_ASSERT(
1793 NS_IsMainThread(),
1794 "Creating ImageBitmap from HTMLImageElement off the main thread.");
1795 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLImageElement(),
1796 aCropRect, aOptions, aRv);
1797 } else if (aSrc.IsSVGImageElement()) {
1798 MOZ_ASSERT(
1799 NS_IsMainThread(),
1800 "Creating ImageBitmap from SVGImageElement off the main thread.");
1801 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsSVGImageElement(),
1802 aCropRect, aOptions, aRv);
1803 } else if (aSrc.IsHTMLVideoElement()) {
1804 MOZ_ASSERT(
1805 NS_IsMainThread(),
1806 "Creating ImageBitmap from HTMLVideoElement off the main thread.");
1807 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(),
1808 aCropRect, aOptions, aRv);
1809 } else if (aSrc.IsHTMLCanvasElement()) {
1810 MOZ_ASSERT(
1811 NS_IsMainThread(),
1812 "Creating ImageBitmap from HTMLCanvasElement off the main thread.");
1813 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(),
1814 aCropRect, aOptions, aRv);
1815 } else if (aSrc.IsOffscreenCanvas()) {
1816 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsOffscreenCanvas(),
1817 aCropRect, aOptions, aRv);
1818 } else if (aSrc.IsImageData()) {
1819 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect,
1820 aOptions, aRv);
1821 } else if (aSrc.IsCanvasRenderingContext2D()) {
1822 MOZ_ASSERT(NS_IsMainThread(),
1823 "Creating ImageBitmap from CanvasRenderingContext2D off the "
1824 "main thread.");
1825 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(),
1826 aCropRect, aOptions, aRv);
1827 } else if (aSrc.IsImageBitmap()) {
1828 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect,
1829 aOptions, aRv);
1830 } else if (aSrc.IsBlob()) {
1831 AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(),
1832 aCropRect, aOptions);
1833 return promise.forget();
1834 } else if (aSrc.IsVideoFrame()) {
1835 imageBitmap = CreateInternal(aGlobal, aSrc.GetAsVideoFrame(), aCropRect,
1836 aOptions, aRv);
1837 } else {
1838 MOZ_CRASH("Unsupported type!");
1839 return nullptr;
1842 if (!aRv.Failed()) {
1843 AsyncFulfillImageBitmapPromise(promise, imageBitmap);
1846 return promise.forget();
1849 /*static*/
1850 JSObject* ImageBitmap::ReadStructuredClone(
1851 JSContext* aCx, JSStructuredCloneReader* aReader, nsIGlobalObject* aParent,
1852 const nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
1853 uint32_t aIndex) {
1854 MOZ_ASSERT(aCx);
1855 MOZ_ASSERT(aReader);
1856 // aParent might be null.
1858 uint32_t picRectX_;
1859 uint32_t picRectY_;
1860 uint32_t picRectWidth_;
1861 uint32_t picRectHeight_;
1862 uint32_t alphaType_;
1863 uint32_t writeOnly;
1865 if (!JS_ReadUint32Pair(aReader, &picRectX_, &picRectY_) ||
1866 !JS_ReadUint32Pair(aReader, &picRectWidth_, &picRectHeight_) ||
1867 !JS_ReadUint32Pair(aReader, &alphaType_, &writeOnly)) {
1868 return nullptr;
1871 int32_t picRectX = BitwiseCast<int32_t>(picRectX_);
1872 int32_t picRectY = BitwiseCast<int32_t>(picRectY_);
1873 int32_t picRectWidth = BitwiseCast<int32_t>(picRectWidth_);
1874 int32_t picRectHeight = BitwiseCast<int32_t>(picRectHeight_);
1875 const auto alphaType = BitwiseCast<gfxAlphaType>(alphaType_);
1877 // Create a new ImageBitmap.
1878 MOZ_ASSERT(!aClonedSurfaces.IsEmpty());
1879 MOZ_ASSERT(aIndex < aClonedSurfaces.Length());
1881 // RefPtr<ImageBitmap> needs to go out of scope before toObjectOrNull() is
1882 // called because the static analysis thinks dereferencing XPCOM objects
1883 // can GC (because in some cases it can!), and a return statement with a
1884 // JSObject* type means that JSObject* is on the stack as a raw pointer
1885 // while destructors are running.
1886 JS::Rooted<JS::Value> value(aCx);
1888 #ifdef FUZZING
1889 if (aIndex >= aClonedSurfaces.Length()) {
1890 return nullptr;
1892 #endif
1893 RefPtr<layers::Image> img = CreateImageFromSurface(aClonedSurfaces[aIndex]);
1894 RefPtr<ImageBitmap> imageBitmap =
1895 new ImageBitmap(aParent, img, !!writeOnly, alphaType);
1897 ErrorResult error;
1898 imageBitmap->SetPictureRect(
1899 IntRect(picRectX, picRectY, picRectWidth, picRectHeight), error);
1900 if (NS_WARN_IF(error.Failed())) {
1901 error.SuppressException();
1902 return nullptr;
1905 if (!GetOrCreateDOMReflector(aCx, imageBitmap, &value)) {
1906 return nullptr;
1909 imageBitmap->mAllocatedImageData = true;
1912 return &(value.toObject());
1915 /*static*/
1916 void ImageBitmap::WriteStructuredClone(
1917 JSStructuredCloneWriter* aWriter,
1918 nsTArray<RefPtr<DataSourceSurface>>& aClonedSurfaces,
1919 ImageBitmap* aImageBitmap, ErrorResult& aRv) {
1920 MOZ_ASSERT(aWriter);
1921 MOZ_ASSERT(aImageBitmap);
1923 if (aImageBitmap->IsWriteOnly()) {
1924 return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, is write-only");
1927 if (!aImageBitmap->mData) {
1928 // A closed image cannot be cloned.
1929 return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, is closed");
1932 const uint32_t picRectX = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.x);
1933 const uint32_t picRectY = BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.y);
1934 const uint32_t picRectWidth =
1935 BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.width);
1936 const uint32_t picRectHeight =
1937 BitwiseCast<uint32_t>(aImageBitmap->mPictureRect.height);
1938 const uint32_t alphaType = BitwiseCast<uint32_t>(aImageBitmap->mAlphaType);
1940 // Indexing the cloned surfaces and send the index to the receiver.
1941 uint32_t index = aClonedSurfaces.Length();
1943 if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, SCTAG_DOM_IMAGEBITMAP, index)) ||
1944 NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectX, picRectY)) ||
1945 NS_WARN_IF(!JS_WriteUint32Pair(aWriter, picRectWidth, picRectHeight)) ||
1946 NS_WARN_IF(
1947 !JS_WriteUint32Pair(aWriter, alphaType, aImageBitmap->mWriteOnly))) {
1948 return aRv.ThrowDataCloneError(
1949 "Cannot clone ImageBitmap, failed to write params");
1952 RefPtr<SourceSurface> surface = aImageBitmap->mData->GetAsSourceSurface();
1953 if (NS_WARN_IF(!surface)) {
1954 return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, no surface");
1957 RefPtr<DataSourceSurface> snapshot = surface->GetDataSurface();
1958 if (NS_WARN_IF(!snapshot)) {
1959 return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, no data surface");
1962 RefPtr<DataSourceSurface> dstDataSurface;
1964 // DataSourceSurfaceD2D1::GetStride() will call EnsureMapped implicitly and
1965 // won't Unmap after exiting function. So instead calling GetStride()
1966 // directly, using ScopedMap to get stride.
1967 DataSourceSurface::ScopedMap map(snapshot, DataSourceSurface::READ);
1968 if (NS_WARN_IF(!map.IsMapped())) {
1969 return aRv.ThrowDataCloneError(
1970 "Cannot clone ImageBitmap, cannot map surface");
1973 dstDataSurface = Factory::CreateDataSourceSurfaceWithStride(
1974 snapshot->GetSize(), snapshot->GetFormat(), map.GetStride(), true);
1976 if (NS_WARN_IF(!dstDataSurface)) {
1977 return aRv.ThrowDataCloneError("Cannot clone ImageBitmap, out of memory");
1979 Factory::CopyDataSourceSurface(snapshot, dstDataSurface);
1980 aClonedSurfaces.AppendElement(dstDataSurface);
1983 size_t ImageBitmap::GetAllocatedSize() const {
1984 if (!mAllocatedImageData) {
1985 return 0;
1988 // Calculate how many bytes are used.
1989 if (mData->GetFormat() == mozilla::ImageFormat::PLANAR_YCBCR) {
1990 return mData->AsPlanarYCbCrImage()->GetDataSize();
1993 if (mData->GetFormat() == mozilla::ImageFormat::NV_IMAGE) {
1994 return mData->AsNVImage()->GetBufferSize();
1997 RefPtr<SourceSurface> surface = mData->GetAsSourceSurface();
1998 if (NS_WARN_IF(!surface)) {
1999 return 0;
2002 const int bytesPerPixel = BytesPerPixel(surface->GetFormat());
2003 return surface->GetSize().height * surface->GetSize().width * bytesPerPixel;
2006 size_t BindingJSObjectMallocBytes(ImageBitmap* aBitmap) {
2007 return aBitmap->GetAllocatedSize();
2010 /* static */
2011 already_AddRefed<CreateImageBitmapFromBlob> CreateImageBitmapFromBlob::Create(
2012 Promise* aPromise, nsIGlobalObject* aGlobal, Blob& aBlob,
2013 const Maybe<IntRect>& aCropRect, nsIEventTarget* aMainThreadEventTarget,
2014 const ImageBitmapOptions& aOptions) {
2015 // Get the internal stream of the blob.
2016 nsCOMPtr<nsIInputStream> stream;
2017 ErrorResult error;
2018 aBlob.Impl()->CreateInputStream(getter_AddRefs(stream), error);
2019 if (NS_WARN_IF(error.Failed())) {
2020 return nullptr;
2023 if (!NS_InputStreamIsBuffered(stream)) {
2024 nsCOMPtr<nsIInputStream> bufferedStream;
2025 nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
2026 stream.forget(), 4096);
2027 if (NS_WARN_IF(NS_FAILED(rv))) {
2028 return nullptr;
2031 stream = bufferedStream;
2034 RefPtr<CreateImageBitmapFromBlob> task = new CreateImageBitmapFromBlob(
2035 aPromise, aGlobal, stream.forget(), aCropRect, aMainThreadEventTarget,
2036 aOptions);
2038 // Nothing to do for the main-thread.
2039 if (NS_IsMainThread()) {
2040 return task.forget();
2043 // Let's use a WorkerRef to keep the worker alive if this is not the
2044 // main-thread.
2045 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
2046 MOZ_ASSERT(workerPrivate);
2048 RefPtr<StrongWorkerRef> workerRef =
2049 StrongWorkerRef::Create(workerPrivate, "CreateImageBitmapFromBlob",
2050 [task]() { task->WorkerShuttingDown(); });
2051 if (NS_WARN_IF(!workerRef)) {
2052 return nullptr;
2055 task->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
2056 return task.forget();
2059 nsresult CreateImageBitmapFromBlob::StartMimeTypeAndDecodeAndCropBlob() {
2060 MOZ_ASSERT(IsCurrentThread());
2062 // Workers.
2063 if (!NS_IsMainThread()) {
2064 RefPtr<CreateImageBitmapFromBlob> self = this;
2065 nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
2066 "CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlob", [self]() {
2067 nsresult rv = self->MimeTypeAndDecodeAndCropBlob();
2068 if (NS_WARN_IF(NS_FAILED(rv))) {
2069 self->MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
2073 return mMainThreadEventTarget->Dispatch(r.forget());
2076 // Main-thread.
2077 return MimeTypeAndDecodeAndCropBlob();
2080 nsresult CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlob() {
2081 MOZ_ASSERT(NS_IsMainThread());
2083 nsAutoCString mimeType;
2084 nsresult rv = GetMimeTypeSync(mimeType);
2085 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
2086 return GetMimeTypeAsync();
2089 if (NS_WARN_IF(NS_FAILED(rv))) {
2090 return rv;
2093 return DecodeAndCropBlob(mimeType);
2096 nsresult CreateImageBitmapFromBlob::DecodeAndCropBlob(
2097 const nsACString& aMimeType) {
2098 // Get the Component object.
2099 nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
2100 if (NS_WARN_IF(!imgtool)) {
2101 return NS_ERROR_FAILURE;
2104 // Decode image.
2105 nsresult rv = imgtool->DecodeImageAsync(mInputStream, aMimeType, this,
2106 mMainThreadEventTarget);
2107 if (NS_WARN_IF(NS_FAILED(rv))) {
2108 return rv;
2111 return NS_OK;
2114 static nsresult sniff_cb(nsIInputStream* aInputStream, void* aClosure,
2115 const char* aFromRawSegment, uint32_t aToOffset,
2116 uint32_t aCount, uint32_t* aWriteCount) {
2117 nsACString* mimeType = static_cast<nsACString*>(aClosure);
2118 MOZ_ASSERT(mimeType);
2120 if (aCount > 0) {
2121 imgLoader::GetMimeTypeFromContent(aFromRawSegment, aCount, *mimeType);
2124 *aWriteCount = 0;
2126 // We don't want to consume data from the stream.
2127 return NS_ERROR_FAILURE;
2130 nsresult CreateImageBitmapFromBlob::GetMimeTypeSync(nsACString& aMimeType) {
2131 uint32_t dummy;
2132 return mInputStream->ReadSegments(sniff_cb, &aMimeType, 128, &dummy);
2135 nsresult CreateImageBitmapFromBlob::GetMimeTypeAsync() {
2136 nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
2137 do_QueryInterface(mInputStream);
2138 if (NS_WARN_IF(!asyncInputStream)) {
2139 // If the stream is not async, why are we here?
2140 return NS_ERROR_FAILURE;
2143 return asyncInputStream->AsyncWait(this, 0, 128, mMainThreadEventTarget);
2146 NS_IMETHODIMP
2147 CreateImageBitmapFromBlob::OnInputStreamReady(nsIAsyncInputStream* aStream) {
2148 // The stream should have data now. Let's start from scratch again.
2149 nsresult rv = MimeTypeAndDecodeAndCropBlob();
2150 if (NS_WARN_IF(NS_FAILED(rv))) {
2151 MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
2154 return NS_OK;
2157 NS_IMETHODIMP
2158 CreateImageBitmapFromBlob::OnImageReady(imgIContainer* aImgContainer,
2159 nsresult aStatus) {
2160 MOZ_ASSERT(NS_IsMainThread());
2162 if (NS_FAILED(aStatus)) {
2163 MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, aStatus);
2164 return NS_OK;
2167 MOZ_ASSERT(aImgContainer);
2169 // Get the surface out.
2170 uint32_t frameFlags =
2171 imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
2172 uint32_t whichFrame = imgIContainer::FRAME_FIRST;
2174 if (mOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
2175 frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
2178 if (mOptions.mColorSpaceConversion == ColorSpaceConversion::None) {
2179 frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
2182 RefPtr<SourceSurface> surface =
2183 aImgContainer->GetFrame(whichFrame, frameFlags);
2185 if (NS_WARN_IF(!surface)) {
2186 MimeTypeAndDecodeAndCropBlobCompletedMainThread(
2187 nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
2188 return NS_OK;
2191 // Crop the source surface if needed.
2192 RefPtr<SourceSurface> croppedSurface = surface;
2193 RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
2195 // force a copy into unprotected memory as a side effect of
2196 // CropAndCopyDataSourceSurface
2197 bool copyRequired = mCropRect.isSome() ||
2198 mOptions.mImageOrientation == ImageOrientation::FlipY;
2200 if (copyRequired) {
2201 // The blob is just decoded into a RasterImage and not optimized yet, so the
2202 // _surface_ we get is a DataSourceSurface which wraps the RasterImage's
2203 // raw buffer.
2205 // The _surface_ might already be optimized so that its type is not
2206 // SurfaceType::DATA. However, we could keep using the generic cropping and
2207 // copying since the decoded buffer is only used in this ImageBitmap so we
2208 // should crop it to save memory usage.
2210 // TODO: Bug1189632 is going to refactor this create-from-blob part to
2211 // decode the blob off the main thread. Re-check if we should do
2212 // cropping at this moment again there.
2214 IntRect cropRect =
2215 mCropRect.isSome() ? mCropRect.ref() : dataSurface->GetRect();
2217 croppedSurface = CropAndCopyDataSourceSurface(dataSurface, cropRect);
2218 if (NS_WARN_IF(!croppedSurface)) {
2219 MimeTypeAndDecodeAndCropBlobCompletedMainThread(
2220 nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
2221 return NS_OK;
2224 dataSurface = croppedSurface->GetDataSurface();
2226 if (mCropRect.isSome()) {
2227 mCropRect->SetRect(0, 0, dataSurface->GetSize().width,
2228 dataSurface->GetSize().height);
2232 if (mOptions.mImageOrientation == ImageOrientation::FlipY) {
2233 croppedSurface = FlipYDataSourceSurface(dataSurface);
2236 if (mOptions.mResizeWidth.WasPassed() || mOptions.mResizeHeight.WasPassed()) {
2237 dataSurface = croppedSurface->GetDataSurface();
2238 croppedSurface = ScaleDataSourceSurface(dataSurface, mOptions);
2239 if (NS_WARN_IF(!croppedSurface)) {
2240 MimeTypeAndDecodeAndCropBlobCompletedMainThread(
2241 nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
2242 return NS_OK;
2244 if (mCropRect.isSome()) {
2245 mCropRect->SetRect(0, 0, croppedSurface->GetSize().width,
2246 croppedSurface->GetSize().height);
2250 if (NS_WARN_IF(!croppedSurface)) {
2251 MimeTypeAndDecodeAndCropBlobCompletedMainThread(
2252 nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
2253 return NS_OK;
2256 // Create an Image from the source surface.
2257 RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface);
2259 if (NS_WARN_IF(!image)) {
2260 MimeTypeAndDecodeAndCropBlobCompletedMainThread(
2261 nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
2262 return NS_OK;
2265 MimeTypeAndDecodeAndCropBlobCompletedMainThread(image, NS_OK);
2266 return NS_OK;
2269 void CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlobCompletedMainThread(
2270 layers::Image* aImage, nsresult aStatus) {
2271 MOZ_ASSERT(NS_IsMainThread());
2273 if (!IsCurrentThread()) {
2274 MutexAutoLock lock(mMutex);
2276 if (!mWorkerRef) {
2277 // The worker is already gone.
2278 return;
2281 RefPtr<CreateImageBitmapFromBlobRunnable> r =
2282 new CreateImageBitmapFromBlobRunnable(mWorkerRef->Private(), this,
2283 aImage, aStatus);
2284 r->Dispatch();
2285 return;
2288 MimeTypeAndDecodeAndCropBlobCompletedOwningThread(aImage, aStatus);
2291 void CreateImageBitmapFromBlob::
2292 MimeTypeAndDecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
2293 nsresult aStatus) {
2294 MOZ_ASSERT(IsCurrentThread());
2296 if (!mPromise) {
2297 // The worker is going to be released soon. No needs to continue.
2298 return;
2301 // Let's release what has to be released on the owning thread.
2302 auto raii = MakeScopeExit([&] {
2303 // Doing this we also release the worker.
2304 mWorkerRef = nullptr;
2306 mPromise = nullptr;
2307 mGlobalObject = nullptr;
2310 if (NS_WARN_IF(NS_FAILED(aStatus))) {
2311 mPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
2312 return;
2315 gfxAlphaType alphaType = gfxAlphaType::Premult;
2317 if (mOptions.mPremultiplyAlpha == PremultiplyAlpha::None) {
2318 alphaType = gfxAlphaType::NonPremult;
2321 // Create ImageBitmap object.
2322 RefPtr<ImageBitmap> imageBitmap =
2323 new ImageBitmap(mGlobalObject, aImage, false /* write-only */, alphaType);
2325 if (mCropRect.isSome()) {
2326 ErrorResult rv;
2327 imageBitmap->SetPictureRect(mCropRect.ref(), rv);
2329 if (rv.Failed()) {
2330 mPromise->MaybeReject(std::move(rv));
2331 return;
2335 imageBitmap->mAllocatedImageData = true;
2337 mPromise->MaybeResolve(imageBitmap);
2340 void CreateImageBitmapFromBlob::WorkerShuttingDown() {
2341 MOZ_ASSERT(IsCurrentThread());
2343 MutexAutoLock lock(mMutex);
2345 // Let's release all the non-thread-safe objects now.
2346 mWorkerRef = nullptr;
2347 mPromise = nullptr;
2348 mGlobalObject = nullptr;
2351 } // namespace mozilla::dom