--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -13,19 +13,22 @@
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/Mutex.h"
#include "mozilla/ScopeExit.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
#include "ImageBitmapColorUtils.h"
#include "ImageBitmapUtils.h"
#include "ImageUtils.h"
+#include "imgLoader.h"
#include "imgTools.h"
using namespace mozilla::gfx;
using namespace mozilla::layers;
using mozilla::dom::HTMLMediaElement_Binding::NETWORK_EMPTY;
using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
namespace mozilla {
@@ -1229,58 +1232,58 @@ AsyncFulfillImageBitmapPromise(Promise*
task->Dispatch(); // Actually, to the current worker-thread.
}
}
class CreateImageBitmapFromBlobRunnable;
class CreateImageBitmapFromBlob final : public CancelableRunnable
, public imgIContainerCallback
+ , public nsIInputStreamCallback
{
friend class CreateImageBitmapFromBlobRunnable;
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_IMGICONTAINERCALLBACK
+ NS_DECL_NSIINPUTSTREAMCALLBACK
static already_AddRefed<CreateImageBitmapFromBlob>
Create(Promise* aPromise,
nsIGlobalObject* aGlobal,
Blob& aBlob,
const Maybe<IntRect>& aCropRect,
nsIEventTarget* aMainThreadEventTarget);
NS_IMETHOD Run() override
{
MOZ_ASSERT(IsCurrentThread());
- nsresult rv = StartDecodeAndCropBlob();
+ nsresult rv = StartMimeTypeAndDecodeAndCropBlob();
if (NS_WARN_IF(NS_FAILED(rv))) {
- DecodeAndCropBlobCompletedMainThread(nullptr, rv);
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
}
return NS_OK;
}
// Called by the WorkerRef.
void WorkerShuttingDown();
private:
CreateImageBitmapFromBlob(Promise* aPromise,
nsIGlobalObject* aGlobal,
already_AddRefed<nsIInputStream> aInputStream,
- const nsACString& aMimeType,
const Maybe<IntRect>& aCropRect,
nsIEventTarget* aMainThreadEventTarget)
: CancelableRunnable("dom::CreateImageBitmapFromBlob")
, mMutex("dom::CreateImageBitmapFromBlob::mMutex")
, mPromise(aPromise)
, mGlobalObject(aGlobal)
, mInputStream(std::move(aInputStream))
- , mMimeType(aMimeType)
, mCropRect(aCropRect)
, mOriginalCropRect(aCropRect)
, mMainThreadEventTarget(aMainThreadEventTarget)
, mThread(GetCurrentVirtualThread())
{
}
virtual ~CreateImageBitmapFromBlob()
@@ -1288,55 +1291,63 @@ private:
}
bool IsCurrentThread() const
{
return mThread == GetCurrentVirtualThread();
}
// Called on the owning thread.
- nsresult StartDecodeAndCropBlob();
+ nsresult StartMimeTypeAndDecodeAndCropBlob();
// Will be called when the decoding + cropping is completed on the
// main-thread. This could the not the owning thread!
- void DecodeAndCropBlobCompletedMainThread(layers::Image* aImage,
- nsresult aStatus);
+ void MimeTypeAndDecodeAndCropBlobCompletedMainThread(layers::Image* aImage,
+ nsresult aStatus);
// Will be called when the decoding + cropping is completed on the owning
// thread.
- void DecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
- nsresult aStatus);
+ void MimeTypeAndDecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
+ nsresult aStatus);
+
+ // This is called on the main-thread only.
+ nsresult MimeTypeAndDecodeAndCropBlob();
// This is called on the main-thread only.
- nsresult DecodeAndCropBlob();
+ nsresult DecodeAndCropBlob(const nsACString& aMimeType);
+
+ // This is called on the main-thread only.
+ nsresult GetMimeTypeSync(nsACString& aMimeType);
+
+ // This is called on the main-thread only.
+ nsresult GetMimeTypeAsync();
Mutex mMutex;
// The access to this object is protected by mutex but is always nullified on
// the owning thread.
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
// Touched only on the owning thread.
RefPtr<Promise> mPromise;
// Touched only on the owning thread.
nsCOMPtr<nsIGlobalObject> mGlobalObject;
nsCOMPtr<nsIInputStream> mInputStream;
- nsCString mMimeType;
Maybe<IntRect> mCropRect;
Maybe<IntRect> mOriginalCropRect;
IntSize mSourceSize;
nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
void* mThread;
};
NS_IMPL_ISUPPORTS_INHERITED(CreateImageBitmapFromBlob, CancelableRunnable,
- imgIContainerCallback)
+ imgIContainerCallback, nsIInputStreamCallback)
class CreateImageBitmapFromBlobRunnable : public WorkerRunnable
{
public:
explicit CreateImageBitmapFromBlobRunnable(WorkerPrivate* aWorkerPrivate,
CreateImageBitmapFromBlob* aTask,
layers::Image* aImage,
nsresult aStatus)
@@ -1344,17 +1355,17 @@ public:
, mTask(aTask)
, mImage(aImage)
, mStatus(aStatus)
{}
bool
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
- mTask->DecodeAndCropBlobCompletedOwningThread(mImage, mStatus);
+ mTask->MimeTypeAndDecodeAndCropBlobCompletedOwningThread(mImage, mStatus);
return true;
}
private:
RefPtr<CreateImageBitmapFromBlob> mTask;
RefPtr<layers::Image> mImage;
nsresult mStatus;
};
@@ -2162,24 +2173,29 @@ CreateImageBitmapFromBlob::Create(Promis
// Get the internal stream of the blob.
nsCOMPtr<nsIInputStream> stream;
ErrorResult error;
aBlob.Impl()->CreateInputStream(getter_AddRefs(stream), error);
if (NS_WARN_IF(error.Failed())) {
return nullptr;
}
- // Get the MIME type string of the blob.
- // The type will be checked in the DecodeImageAsync() method.
- nsAutoString mimeTypeUTF16;
- aBlob.Impl()->GetType(mimeTypeUTF16);
- NS_ConvertUTF16toUTF8 mimeType(mimeTypeUTF16);
+ if (!NS_InputStreamIsBuffered(stream)) {
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ nsresult rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ stream.forget(), 4096);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ stream = bufferedStream;
+ }
RefPtr<CreateImageBitmapFromBlob> task =
- new CreateImageBitmapFromBlob(aPromise, aGlobal, stream.forget(), mimeType,
+ new CreateImageBitmapFromBlob(aPromise, aGlobal, stream.forget(),
aCropRect, aMainThreadEventTarget);
// Nothing to do for the main-thread.
if (NS_IsMainThread()) {
return task.forget();
}
// Let's use a WorkerRef to keep the worker alive if this is not the
@@ -2196,86 +2212,150 @@ CreateImageBitmapFromBlob::Create(Promis
return nullptr;
}
task->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
return task.forget();
}
nsresult
-CreateImageBitmapFromBlob::StartDecodeAndCropBlob()
+CreateImageBitmapFromBlob::StartMimeTypeAndDecodeAndCropBlob()
{
MOZ_ASSERT(IsCurrentThread());
// Workers.
if (!NS_IsMainThread()) {
RefPtr<CreateImageBitmapFromBlob> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
- "CreateImageBitmapFromBlob::DecodeAndCropBlob",
+ "CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlob",
[self]() {
- nsresult rv = self->DecodeAndCropBlob();
+ nsresult rv = self->MimeTypeAndDecodeAndCropBlob();
if (NS_WARN_IF(NS_FAILED(rv))) {
- self->DecodeAndCropBlobCompletedMainThread(nullptr, rv);
+ self->MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, rv);
}
});
return mMainThreadEventTarget->Dispatch(r.forget());
}
// Main-thread.
- return DecodeAndCropBlob();
+ return MimeTypeAndDecodeAndCropBlob();
}
nsresult
-CreateImageBitmapFromBlob::DecodeAndCropBlob()
+CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlob()
{
MOZ_ASSERT(NS_IsMainThread());
+ nsAutoCString mimeType;
+ nsresult rv = GetMimeTypeSync(mimeType);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ return GetMimeTypeAsync();
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return DecodeAndCropBlob(mimeType);
+}
+
+nsresult
+CreateImageBitmapFromBlob::DecodeAndCropBlob(const nsACString& aMimeType)
+{
// Get the Component object.
nsCOMPtr<imgITools> imgtool = do_GetService(NS_IMGTOOLS_CID);
if (NS_WARN_IF(!imgtool)) {
return NS_ERROR_FAILURE;
}
// Decode image.
- nsCOMPtr<imgIContainer> imgContainer;
- nsresult rv = imgtool->DecodeImageAsync(mInputStream, mMimeType, this,
+ nsresult rv = imgtool->DecodeImageAsync(mInputStream, aMimeType, this,
mMainThreadEventTarget);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
+static nsresult
+sniff_cb(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* aWriteCount)
+{
+ nsACString* mimeType = static_cast<nsACString*>(aClosure);
+ MOZ_ASSERT(mimeType);
+
+ if (aCount > 0) {
+ imgLoader::GetMimeTypeFromContent(aFromRawSegment, aCount, *mimeType);
+ }
+
+ *aWriteCount = 0;
+
+ // We don't want to consume data from the stream.
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+CreateImageBitmapFromBlob::GetMimeTypeSync(nsACString& aMimeType)
+{
+ uint32_t dummy;
+ return mInputStream->ReadSegments(sniff_cb, &aMimeType, 128, &dummy);
+}
+
+nsresult
+CreateImageBitmapFromBlob::GetMimeTypeAsync()
+{
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(mInputStream);
+ if (NS_WARN_IF(!asyncInputStream)) {
+ // If the stream is not async, why are we here?
+ return NS_ERROR_FAILURE;
+ }
+
+ return asyncInputStream->AsyncWait(this, 0, 128, mMainThreadEventTarget);
+}
+
+NS_IMETHODIMP
+CreateImageBitmapFromBlob::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+ // The stream should have data now. Let's start from scratch again.
+ return MimeTypeAndDecodeAndCropBlob();
+}
+
NS_IMETHODIMP
CreateImageBitmapFromBlob::OnImageReady(imgIContainer* aImgContainer,
nsresult aStatus)
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(aStatus)) {
- DecodeAndCropBlobCompletedMainThread(nullptr, aStatus);
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr, aStatus);
return NS_OK;
}
MOZ_ASSERT(aImgContainer);
// Get the surface out.
uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_WANT_DATA_SURFACE;
uint32_t whichFrame = imgIContainer::FRAME_FIRST;
RefPtr<SourceSurface> surface = aImgContainer->GetFrame(whichFrame, frameFlags);
if (NS_WARN_IF(!surface)) {
- DecodeAndCropBlobCompletedMainThread(nullptr,
- NS_ERROR_DOM_INVALID_STATE_ERR);
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr,
+ NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_OK;
}
- // Store the sourceSize value for the DecodeAndCropBlobCompletedMainThread call.
+ // Store the sourceSize value for the
+ // MimeTypeAndDecodeAndCropBlobCompletedMainThread call.
mSourceSize = surface->GetSize();
// Crop the source surface if needed.
RefPtr<SourceSurface> croppedSurface = surface;
if (mCropRect.isSome()) {
// The blob is just decoded into a RasterImage and not optimized yet, so the
// _surface_ we get is a DataSourceSurface which wraps the RasterImage's
@@ -2290,37 +2370,37 @@ CreateImageBitmapFromBlob::OnImageReady(
// decode the blob off the main thread. Re-check if we should do
// cropping at this moment again there.
RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
croppedSurface = CropAndCopyDataSourceSurface(dataSurface, mCropRect.ref());
mCropRect->MoveTo(0, 0);
}
if (NS_WARN_IF(!croppedSurface)) {
- DecodeAndCropBlobCompletedMainThread(nullptr,
- NS_ERROR_DOM_INVALID_STATE_ERR);
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr,
+ NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_OK;
}
// Create an Image from the source surface.
RefPtr<layers::Image> image = CreateImageFromSurface(croppedSurface);
if (NS_WARN_IF(!image)) {
- DecodeAndCropBlobCompletedMainThread(nullptr,
- NS_ERROR_DOM_INVALID_STATE_ERR);
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(nullptr,
+ NS_ERROR_DOM_INVALID_STATE_ERR);
return NS_OK;
}
- DecodeAndCropBlobCompletedMainThread(image, NS_OK);
+ MimeTypeAndDecodeAndCropBlobCompletedMainThread(image, NS_OK);
return NS_OK;
}
void
-CreateImageBitmapFromBlob::DecodeAndCropBlobCompletedMainThread(layers::Image* aImage,
- nsresult aStatus)
+CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlobCompletedMainThread(layers::Image* aImage,
+ nsresult aStatus)
{
MOZ_ASSERT(NS_IsMainThread());
if (!IsCurrentThread()) {
MutexAutoLock lock(mMutex);
if (!mWorkerRef) {
// The worker is already gone.
@@ -2329,22 +2409,22 @@ CreateImageBitmapFromBlob::DecodeAndCrop
RefPtr<CreateImageBitmapFromBlobRunnable> r =
new CreateImageBitmapFromBlobRunnable(mWorkerRef->Private(),
this, aImage, aStatus);
r->Dispatch();
return;
}
- DecodeAndCropBlobCompletedOwningThread(aImage, aStatus);
+ MimeTypeAndDecodeAndCropBlobCompletedOwningThread(aImage, aStatus);
}
void
-CreateImageBitmapFromBlob::DecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
- nsresult aStatus)
+CreateImageBitmapFromBlob::MimeTypeAndDecodeAndCropBlobCompletedOwningThread(layers::Image* aImage,
+ nsresult aStatus)
{
MOZ_ASSERT(IsCurrentThread());
if (!mPromise) {
// The worker is going to be released soon. No needs to continue.
return;
}
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/test_invalid_mime_type_blob.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<title>createImageBitmap from Blob with invalid type</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<script>
+
+function syncBlob() {
+ info("Let's create a small memory blob...");
+
+ // A 1x1 PNG image.
+ // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+ const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+
+ let bytes = new Array(IMAGE.length);
+ for (let i = 0; i < IMAGE.length; i++) {
+ bytes[i] = IMAGE.charCodeAt(i);
+ }
+
+ let blob = new Blob([new Uint8Array(bytes)], { type: "text/html"});
+ window.createImageBitmap(blob)
+ .then(imageBitmap => {
+ ok(true, "Image created!");
+ is(imageBitmap.width, 1, "Image is 1x1");
+ is(imageBitmap.height, 1, "Image is 1x1");
+ })
+ .then(next);
+}
+
+function asyncBlob() {
+ info("Let's create a big memory blob...");
+
+ // A 1x1 PNG image.
+ // Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+ const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+
+ let bytes = new Array(IMAGE.length);
+ for (let i = 0; i < IMAGE.length; i++) {
+ bytes[i] = IMAGE.charCodeAt(i);
+ }
+
+ let array = [];
+ for (let i = 0; i < 20000; ++i) {
+ array.push(new Uint8Array(bytes));
+ }
+
+ let blob = new Blob(array, { type: "text/html"});
+ ok(blob.size > 1000000, "More than 1mb");
+
+ let bc = new BroadcastChannel('a');
+ bc.onmessage = e => {
+ window.createImageBitmap(e.data)
+ .then(imageBitmap => {
+ ok(true, "Image created!");
+ is(imageBitmap.width, 1, "Image is 1x1");
+ is(imageBitmap.height, 1, "Image is 1x1");
+ })
+ .then(next);
+ }
+
+ new BroadcastChannel('a').postMessage(blob);
+}
+
+let tests = [
+ syncBlob,
+ asyncBlob,
+];
+
+function next() {
+ if (tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+
+ let test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>