diff options
Diffstat (limited to 'dom/base/MutableBlobStorage.cpp')
-rw-r--r-- | dom/base/MutableBlobStorage.cpp | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/dom/base/MutableBlobStorage.cpp b/dom/base/MutableBlobStorage.cpp new file mode 100644 index 0000000000..54ca4fe39b --- /dev/null +++ b/dom/base/MutableBlobStorage.cpp @@ -0,0 +1,643 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/MutableBlobStorage.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/File.h" +#include "mozilla/TaskQueue.h" +#include "nsAnonymousTemporaryFile.h" +#include "nsNetCID.h" +#include "WorkerPrivate.h" + +#define BLOB_MEMORY_TEMPORARY_FILE 1048576 + +namespace mozilla { +namespace dom { + +namespace { + +// This class uses the callback to inform when the Blob is created or when the +// error must be propagated. +class BlobCreationDoneRunnable final : public Runnable +{ +public: + BlobCreationDoneRunnable(MutableBlobStorage* aBlobStorage, + MutableBlobStorageCallback* aCallback, + Blob* aBlob, + nsresult aRv) + : mBlobStorage(aBlobStorage) + , mCallback(aCallback) + , mBlob(aBlob) + , mRv(aRv) + { + MOZ_ASSERT(aBlobStorage); + MOZ_ASSERT(aCallback); + MOZ_ASSERT((NS_FAILED(aRv) && !aBlob) || + (NS_SUCCEEDED(aRv) && aBlob)); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + mCallback->BlobStoreCompleted(mBlobStorage, mBlob, mRv); + mCallback = nullptr; + mBlob = nullptr; + return NS_OK; + } + +private: + ~BlobCreationDoneRunnable() + { + // If something when wrong, we still have to release these objects in the + // correct thread. + NS_ReleaseOnMainThread(mCallback.forget()); + NS_ReleaseOnMainThread(mBlob.forget()); + } + + RefPtr<MutableBlobStorage> mBlobStorage; + RefPtr<MutableBlobStorageCallback> mCallback; + RefPtr<Blob> mBlob; + nsresult mRv; +}; + +// This runnable goes back to the main-thread and informs the BlobStorage about +// the temporary file. +class FileCreatedRunnable final : public Runnable +{ +public: + FileCreatedRunnable(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD) + : mBlobStorage(aBlobStorage) + , mFD(aFD) + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aBlobStorage); + MOZ_ASSERT(aFD); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + mBlobStorage->TemporaryFileCreated(mFD); + mFD = nullptr; + return NS_OK; + } + +private: + ~FileCreatedRunnable() + { + // If something when wrong, we still have to close the FileDescriptor. + if (mFD) { + PR_Close(mFD); + } + } + + RefPtr<MutableBlobStorage> mBlobStorage; + PRFileDesc* mFD; +}; + +// This runnable creates the temporary file. When done, FileCreatedRunnable is +// dispatched back to the main-thread. +class CreateTemporaryFileRunnable final : public Runnable +{ +public: + explicit CreateTemporaryFileRunnable(MutableBlobStorage* aBlobStorage) + : mBlobStorage(aBlobStorage) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBlobStorage); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(!NS_IsMainThread()); + + PRFileDesc* tempFD = nullptr; + nsresult rv = NS_OpenAnonymousTemporaryFile(&tempFD); + if (NS_WARN_IF(NS_FAILED(rv))) { + // In sandboxed context we are not allowed to create temporary files, but + // this doesn't mean that BlobStorage should fail. We can continue to + // store data in memory. We don't change the storageType so that we don't + // try to create a temporary file again. + return NS_OK; + } + + // The ownership of the tempFD is moved to the FileCreatedRunnable. + return NS_DispatchToMainThread(new FileCreatedRunnable(mBlobStorage, tempFD)); + } + +private: + RefPtr<MutableBlobStorage> mBlobStorage; +}; + +// Simple runnable to propagate the error to the BlobStorage. +class ErrorPropagationRunnable final : public Runnable +{ +public: + ErrorPropagationRunnable(MutableBlobStorage* aBlobStorage, nsresult aRv) + : mBlobStorage(aBlobStorage) + , mRv(aRv) + {} + + NS_IMETHOD + Run() override + { + mBlobStorage->ErrorPropagated(mRv); + return NS_OK; + } + +private: + RefPtr<MutableBlobStorage> mBlobStorage; + nsresult mRv; +}; + +// This runnable moves a buffer to the IO thread and there, it writes it into +// the temporary file. +class WriteRunnable final : public Runnable +{ +public: + static WriteRunnable* + CopyBuffer(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD, + const void* aData, uint32_t aLength) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBlobStorage); + MOZ_ASSERT(aFD); + MOZ_ASSERT(aData); + + // We have to take a copy of this buffer. + void* data = malloc(aLength); + if (!data) { + return nullptr; + } + + memcpy((char*)data, aData, aLength); + return new WriteRunnable(aBlobStorage, aFD, data, aLength); + } + + static WriteRunnable* + AdoptBuffer(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD, + void* aData, uint32_t aLength) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBlobStorage); + MOZ_ASSERT(aFD); + MOZ_ASSERT(aData); + + return new WriteRunnable(aBlobStorage, aFD, aData, aLength); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(!NS_IsMainThread()); + + int32_t written = PR_Write(mFD, mData, mLength); + if (NS_WARN_IF(written < 0 || uint32_t(written) != mLength)) { + return NS_DispatchToMainThread( + new ErrorPropagationRunnable(mBlobStorage, NS_ERROR_FAILURE)); + } + + return NS_OK; + } + +private: + WriteRunnable(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD, + void* aData, uint32_t aLength) + : mBlobStorage(aBlobStorage) + , mFD(aFD) + , mData(aData) + , mLength(aLength) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mBlobStorage); + MOZ_ASSERT(aFD); + MOZ_ASSERT(aData); + } + + ~WriteRunnable() + { + free(mData); + } + + RefPtr<MutableBlobStorage> mBlobStorage; + PRFileDesc* mFD; + void* mData; + uint32_t mLength; +}; + +// This runnable closes the FD in case something goes wrong or the temporary +// file is not needed anymore. +class CloseFileRunnable final : public Runnable +{ +public: + explicit CloseFileRunnable(PRFileDesc* aFD) + : mFD(aFD) + {} + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(!NS_IsMainThread()); + PR_Close(mFD); + mFD = nullptr; + return NS_OK; + } + +private: + ~CloseFileRunnable() + { + if (mFD) { + PR_Close(mFD); + } + } + + PRFileDesc* mFD; +}; + +// This runnable is dispatched to the main-thread from the IO thread and its +// task is to create the blob and inform the callback. +class CreateBlobRunnable final : public Runnable +{ +public: + CreateBlobRunnable(MutableBlobStorage* aBlobStorage, + already_AddRefed<nsISupports> aParent, + const nsACString& aContentType, + already_AddRefed<MutableBlobStorageCallback> aCallback) + : mBlobStorage(aBlobStorage) + , mParent(aParent) + , mContentType(aContentType) + , mCallback(aCallback) + { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + mBlobStorage->CreateBlobAndRespond(mParent.forget(), mContentType, + mCallback.forget()); + return NS_OK; + } + +private: + ~CreateBlobRunnable() + { + // If something when wrong, we still have to release data in the correct + // thread. + NS_ReleaseOnMainThread(mParent.forget()); + NS_ReleaseOnMainThread(mCallback.forget()); + } + + RefPtr<MutableBlobStorage> mBlobStorage; + nsCOMPtr<nsISupports> mParent; + nsCString mContentType; + RefPtr<MutableBlobStorageCallback> mCallback; +}; + +// This task is used to know when the writing is completed. From the IO thread +// it dispatches a CreateBlobRunnable to the main-thread. +class LastRunnable final : public Runnable +{ +public: + LastRunnable(MutableBlobStorage* aBlobStorage, + nsISupports* aParent, + const nsACString& aContentType, + MutableBlobStorageCallback* aCallback) + : mBlobStorage(aBlobStorage) + , mParent(aParent) + , mContentType(aContentType) + , mCallback(aCallback) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mBlobStorage); + MOZ_ASSERT(aCallback); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(!NS_IsMainThread()); + RefPtr<Runnable> runnable = + new CreateBlobRunnable(mBlobStorage, mParent.forget(), + mContentType, mCallback.forget()); + return NS_DispatchToMainThread(runnable); + } + +private: + ~LastRunnable() + { + // If something when wrong, we still have to release data in the correct + // thread. + NS_ReleaseOnMainThread(mParent.forget()); + NS_ReleaseOnMainThread(mCallback.forget()); + } + + RefPtr<MutableBlobStorage> mBlobStorage; + nsCOMPtr<nsISupports> mParent; + nsCString mContentType; + RefPtr<MutableBlobStorageCallback> mCallback; +}; + +} // anonymous namespace + +MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType) + : mData(nullptr) + , mDataLen(0) + , mDataBufferLen(0) + , mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory) + , mFD(nullptr) + , mErrorResult(NS_OK) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +MutableBlobStorage::~MutableBlobStorage() +{ + free(mData); + + if (mFD) { + RefPtr<Runnable> runnable = new CloseFileRunnable(mFD); + DispatchToIOThread(runnable.forget()); + } + + if (mTaskQueue) { + mTaskQueue->BeginShutdown(); + } +} + +uint64_t +MutableBlobStorage::GetBlobWhenReady(nsISupports* aParent, + const nsACString& aContentType, + MutableBlobStorageCallback* aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCallback); + + // GetBlob can be called just once. + MOZ_ASSERT(mStorageState != eClosed); + StorageState previousState = mStorageState; + mStorageState = eClosed; + + if (previousState == eInTemporaryFile) { + MOZ_ASSERT(mFD); + + if (NS_FAILED(mErrorResult)) { + RefPtr<Runnable> runnable = + new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult); + NS_DispatchToMainThread(runnable.forget()); + return 0; + } + + // We want to wait until all the WriteRunnable are completed. The way we do + // this is to go to the I/O thread and then we come back: the runnables are + // executed in order and this LastRunnable will be... the last one. + RefPtr<Runnable> runnable = + new LastRunnable(this, aParent, aContentType, aCallback); + DispatchToIOThread(runnable.forget()); + return mDataLen; + } + + // If we are waiting for the temporary file, it's better to wait... + if (previousState == eWaitingForTemporaryFile) { + mPendingParent = aParent; + mPendingContentType = aContentType; + mPendingCallback = aCallback; + return mDataLen; + } + + RefPtr<BlobImpl> blobImpl; + + if (mData) { + blobImpl = new BlobImplMemory(mData, mDataLen, + NS_ConvertUTF8toUTF16(aContentType)); + + mData = nullptr; // The BlobImplMemory takes ownership of the buffer + mDataLen = 0; + mDataBufferLen = 0; + } else { + blobImpl = new EmptyBlobImpl(NS_ConvertUTF8toUTF16(aContentType)); + } + + RefPtr<Blob> blob = Blob::Create(aParent, blobImpl); + RefPtr<BlobCreationDoneRunnable> runnable = + new BlobCreationDoneRunnable(this, aCallback, blob, NS_OK); + + nsresult error = NS_DispatchToMainThread(runnable); + if (NS_WARN_IF(NS_FAILED(error))) { + return 0; + } + + return mDataLen; +} + +nsresult +MutableBlobStorage::Append(const void* aData, uint32_t aLength) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mStorageState != eClosed); + NS_ENSURE_ARG_POINTER(aData); + + if (!aLength) { + return NS_OK; + } + + // If eInMemory is the current Storage state, we could maybe migrate to + // a temporary file. + if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength)) { + nsresult rv = MaybeCreateTemporaryFile(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // If we are already in the temporaryFile mode, we have to dispatch a + // runnable. + if (mStorageState == eInTemporaryFile) { + MOZ_ASSERT(mFD); + + RefPtr<WriteRunnable> runnable = + WriteRunnable::CopyBuffer(this, mFD, aData, aLength); + if (NS_WARN_IF(!runnable)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DispatchToIOThread(runnable.forget()); + + mDataLen += aLength; + return NS_OK; + } + + // By default, we store in memory. + + uint64_t offset = mDataLen; + + if (!ExpandBufferSize(aLength)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + memcpy((char*)mData + offset, aData, aLength); + return NS_OK; +} + +bool +MutableBlobStorage::ExpandBufferSize(uint64_t aSize) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mStorageState < eInTemporaryFile); + + if (mDataBufferLen >= mDataLen + aSize) { + mDataLen += aSize; + return true; + } + + // Start at 1 or we'll loop forever. + CheckedUint32 bufferLen = + std::max<uint32_t>(static_cast<uint32_t>(mDataBufferLen), 1); + while (bufferLen.isValid() && bufferLen.value() < mDataLen + aSize) { + bufferLen *= 2; + } + + if (!bufferLen.isValid()) { + return false; + } + + void* data = realloc(mData, bufferLen.value()); + if (!data) { + return false; + } + + mData = data; + mDataBufferLen = bufferLen.value(); + mDataLen += aSize; + return true; +} + +bool +MutableBlobStorage::ShouldBeTemporaryStorage(uint64_t aSize) const +{ + MOZ_ASSERT(mStorageState == eInMemory); + + CheckedUint32 bufferSize = mDataLen; + bufferSize += aSize; + + if (!bufferSize.isValid()) { + return false; + } + + return bufferSize.value() >= Preferences::GetUint("dom.blob.memoryToTemporaryFile", + BLOB_MEMORY_TEMPORARY_FILE); +} + +nsresult +MutableBlobStorage::MaybeCreateTemporaryFile() +{ + RefPtr<Runnable> runnable = new CreateTemporaryFileRunnable(this); + DispatchToIOThread(runnable.forget()); + + mStorageState = eWaitingForTemporaryFile; + return NS_OK; +} + +void +MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile || + mStorageState == eClosed); + MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed); + + // If the object has been already closed and we don't need to execute a + // callback, we need just to close the file descriptor in the correct thread. + if (mStorageState == eClosed && !mPendingCallback) { + RefPtr<Runnable> runnable = new CloseFileRunnable(aFD); + DispatchToIOThread(runnable.forget()); + return; + } + + // If we still receiving data, we can proceed in temporary-file mode. + if (mStorageState == eWaitingForTemporaryFile) { + mStorageState = eInTemporaryFile; + } + + mFD = aFD; + + // This runnable takes the ownership of mData and it will write this buffer + // into the temporary file. + RefPtr<WriteRunnable> runnable = + WriteRunnable::AdoptBuffer(this, mFD, mData, mDataLen); + MOZ_ASSERT(runnable); + + mData = nullptr; + + DispatchToIOThread(runnable.forget()); + + // If we are closed, it means that GetBlobWhenReady() has been called when we + // were already waiting for a temporary file-descriptor. Finally we are here, + // AdoptBuffer runnable is going to write the current buffer into this file. + // After that, there is nothing else to write, and we dispatch LastRunnable + // which ends up calling mPendingCallback via CreateBlobRunnable. + if (mStorageState == eClosed) { + MOZ_ASSERT(mPendingCallback); + + RefPtr<Runnable> runnable = + new LastRunnable(this, mPendingParent, mPendingContentType, + mPendingCallback); + DispatchToIOThread(runnable.forget()); + + mPendingParent = nullptr; + mPendingCallback = nullptr; + } +} + +void +MutableBlobStorage::CreateBlobAndRespond(already_AddRefed<nsISupports> aParent, + const nsACString& aContentType, + already_AddRefed<MutableBlobStorageCallback> aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mStorageState == eClosed); + MOZ_ASSERT(mFD); + + nsCOMPtr<nsISupports> parent(aParent); + RefPtr<MutableBlobStorageCallback> callback(aCallback); + + RefPtr<Blob> blob = + File::CreateTemporaryBlob(parent, mFD, 0, mDataLen, + NS_ConvertUTF8toUTF16(aContentType)); + callback->BlobStoreCompleted(this, blob, NS_OK); + + // ownership of this FD is moved to the BlobImpl. + mFD = nullptr; +} + +void +MutableBlobStorage::ErrorPropagated(nsresult aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + mErrorResult = aRv; +} + +void +MutableBlobStorage::DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable) +{ + if (!mTaskQueue) { + nsCOMPtr<nsIEventTarget> target + = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + mTaskQueue = new TaskQueue(target.forget()); + } + + nsCOMPtr<nsIRunnable> runnable(aRunnable); + mTaskQueue->Dispatch(runnable.forget()); +} + +} // dom namespace +} // mozilla namespace |