forked from mirrors/gecko-dev
		
	 d7888947e5
			
		
	
	
		d7888947e5
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D49436 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			669 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			669 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "EmptyBlobImpl.h"
 | |
| #include "MutableBlobStorage.h"
 | |
| #include "MemoryBlobImpl.h"
 | |
| #include "mozilla/CheckedInt.h"
 | |
| #include "mozilla/dom/TemporaryIPCBlobChild.h"
 | |
| #include "mozilla/dom/WorkerPrivate.h"
 | |
| #include "mozilla/ipc/BackgroundChild.h"
 | |
| #include "mozilla/ipc/PBackgroundChild.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/TaskQueue.h"
 | |
| #include "File.h"
 | |
| #include "nsAnonymousTemporaryFile.h"
 | |
| #include "nsNetCID.h"
 | |
| #include "nsProxyRelease.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,
 | |
|                            BlobImpl* aBlobImpl, nsresult aRv)
 | |
|       : Runnable("dom::BlobCreationDoneRunnable"),
 | |
|         mBlobStorage(aBlobStorage),
 | |
|         mCallback(aCallback),
 | |
|         mBlobImpl(aBlobImpl),
 | |
|         mRv(aRv) {
 | |
|     MOZ_ASSERT(aBlobStorage);
 | |
|     MOZ_ASSERT(aCallback);
 | |
|     MOZ_ASSERT((NS_FAILED(aRv) && !aBlobImpl) ||
 | |
|                (NS_SUCCEEDED(aRv) && aBlobImpl));
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Run() override {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
|     mCallback->BlobStoreCompleted(mBlobStorage, mBlobImpl, mRv);
 | |
|     mCallback = nullptr;
 | |
|     mBlobImpl = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~BlobCreationDoneRunnable() {
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
|     // If something when wrong, we still have to release these objects in the
 | |
|     // correct thread.
 | |
|     NS_ProxyRelease("BlobCreationDoneRunnable::mCallback",
 | |
|                     mBlobStorage->EventTarget(), mCallback.forget());
 | |
|   }
 | |
| 
 | |
|   RefPtr<MutableBlobStorage> mBlobStorage;
 | |
|   RefPtr<MutableBlobStorageCallback> mCallback;
 | |
|   RefPtr<BlobImpl> mBlobImpl;
 | |
|   nsresult mRv;
 | |
| };
 | |
| 
 | |
| // Simple runnable to propagate the error to the BlobStorage.
 | |
| class ErrorPropagationRunnable final : public Runnable {
 | |
|  public:
 | |
|   ErrorPropagationRunnable(MutableBlobStorage* aBlobStorage, nsresult aRv)
 | |
|       : Runnable("dom::ErrorPropagationRunnable"),
 | |
|         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, if its File Descriptor has not been already closed.
 | |
| class WriteRunnable final : public Runnable {
 | |
|  public:
 | |
|   static WriteRunnable* CopyBuffer(MutableBlobStorage* aBlobStorage,
 | |
|                                    const void* aData, uint32_t aLength) {
 | |
|     MOZ_ASSERT(aBlobStorage);
 | |
|     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, data, aLength);
 | |
|   }
 | |
| 
 | |
|   static WriteRunnable* AdoptBuffer(MutableBlobStorage* aBlobStorage,
 | |
|                                     void* aData, uint32_t aLength) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(aBlobStorage);
 | |
|     MOZ_ASSERT(aData);
 | |
| 
 | |
|     return new WriteRunnable(aBlobStorage, aData, aLength);
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Run() override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
| 
 | |
|     PRFileDesc* fd = mBlobStorage->GetFD();
 | |
|     if (!fd) {
 | |
|       // The file descriptor has been closed in the meantime.
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     int32_t written = PR_Write(fd, mData, mLength);
 | |
|     if (NS_WARN_IF(written < 0 || uint32_t(written) != mLength)) {
 | |
|       mBlobStorage->CloseFD();
 | |
|       return mBlobStorage->EventTarget()->Dispatch(
 | |
|           new ErrorPropagationRunnable(mBlobStorage, NS_ERROR_FAILURE),
 | |
|           NS_DISPATCH_NORMAL);
 | |
|     }
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   WriteRunnable(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength)
 | |
|       : Runnable("dom::WriteRunnable"),
 | |
|         mBlobStorage(aBlobStorage),
 | |
|         mData(aData),
 | |
|         mLength(aLength) {
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
|     MOZ_ASSERT(aData);
 | |
|   }
 | |
| 
 | |
|   ~WriteRunnable() { free(mData); }
 | |
| 
 | |
|   RefPtr<MutableBlobStorage> mBlobStorage;
 | |
|   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)
 | |
|       : Runnable("dom::CloseFileRunnable"), 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 TemporaryIPCBlobChildCallback {
 | |
|  public:
 | |
|   // We need to always declare refcounting because
 | |
|   // TemporaryIPCBlobChildCallback has pure-virtual refcounting.
 | |
|   NS_DECL_ISUPPORTS_INHERITED
 | |
| 
 | |
|   CreateBlobRunnable(MutableBlobStorage* aBlobStorage,
 | |
|                      const nsACString& aContentType,
 | |
|                      already_AddRefed<MutableBlobStorageCallback> aCallback)
 | |
|       : Runnable("dom::CreateBlobRunnable"),
 | |
|         mBlobStorage(aBlobStorage),
 | |
|         mContentType(aContentType),
 | |
|         mCallback(aCallback) {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
|     MOZ_ASSERT(aBlobStorage);
 | |
|   }
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   Run() override {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
|     mBlobStorage->AskForBlob(this, mContentType);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   void OperationSucceeded(BlobImpl* aBlobImpl) override {
 | |
|     RefPtr<MutableBlobStorageCallback> callback(std::move(mCallback));
 | |
|     callback->BlobStoreCompleted(mBlobStorage, aBlobImpl, NS_OK);
 | |
|   }
 | |
| 
 | |
|   void OperationFailed(nsresult aRv) override {
 | |
|     RefPtr<MutableBlobStorageCallback> callback(std::move(mCallback));
 | |
|     callback->BlobStoreCompleted(mBlobStorage, nullptr, aRv);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~CreateBlobRunnable() {
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
|     // If something when wrong, we still have to release data in the correct
 | |
|     // thread.
 | |
|     NS_ProxyRelease("CreateBlobRunnable::mCallback",
 | |
|                     mBlobStorage->EventTarget(), mCallback.forget());
 | |
|   }
 | |
| 
 | |
|   RefPtr<MutableBlobStorage> mBlobStorage;
 | |
|   nsCString mContentType;
 | |
|   RefPtr<MutableBlobStorageCallback> mCallback;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS_INHERITED0(CreateBlobRunnable, Runnable)
 | |
| 
 | |
| // 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, const nsACString& aContentType,
 | |
|                MutableBlobStorageCallback* aCallback)
 | |
|       : Runnable("dom::LastRunnable"),
 | |
|         mBlobStorage(aBlobStorage),
 | |
|         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, mContentType, mCallback.forget());
 | |
|     return mBlobStorage->EventTarget()->Dispatch(runnable, NS_DISPATCH_NORMAL);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   ~LastRunnable() {
 | |
|     MOZ_ASSERT(mBlobStorage);
 | |
|     // If something when wrong, we still have to release data in the correct
 | |
|     // thread.
 | |
|     NS_ProxyRelease("LastRunnable::mCallback", mBlobStorage->EventTarget(),
 | |
|                     mCallback.forget());
 | |
|   }
 | |
| 
 | |
|   RefPtr<MutableBlobStorage> mBlobStorage;
 | |
|   nsCString mContentType;
 | |
|   RefPtr<MutableBlobStorageCallback> mCallback;
 | |
| };
 | |
| 
 | |
| }  // anonymous namespace
 | |
| 
 | |
| MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType,
 | |
|                                        nsIEventTarget* aEventTarget,
 | |
|                                        uint32_t aMaxMemory)
 | |
|     : mMutex("MutableBlobStorage::mMutex"),
 | |
|       mData(nullptr),
 | |
|       mDataLen(0),
 | |
|       mDataBufferLen(0),
 | |
|       mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory),
 | |
|       mFD(nullptr),
 | |
|       mErrorResult(NS_OK),
 | |
|       mEventTarget(aEventTarget),
 | |
|       mMaxMemory(aMaxMemory) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   if (!mEventTarget) {
 | |
|     mEventTarget = GetMainThreadEventTarget();
 | |
|   }
 | |
| 
 | |
|   if (aMaxMemory == 0 && aType == eCouldBeInTemporaryFile) {
 | |
|     mMaxMemory = Preferences::GetUint("dom.blob.memoryToTemporaryFile",
 | |
|                                       BLOB_MEMORY_TEMPORARY_FILE);
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mEventTarget);
 | |
| }
 | |
| 
 | |
| MutableBlobStorage::~MutableBlobStorage() {
 | |
|   free(mData);
 | |
| 
 | |
|   if (mFD) {
 | |
|     RefPtr<Runnable> runnable = new CloseFileRunnable(mFD);
 | |
|     Unused << DispatchToIOThread(runnable.forget());
 | |
|   }
 | |
| 
 | |
|   if (mTaskQueue) {
 | |
|     mTaskQueue->BeginShutdown();
 | |
|   }
 | |
| 
 | |
|   if (mActor) {
 | |
|     NS_ProxyRelease("MutableBlobStorage::mActor", EventTarget(),
 | |
|                     mActor.forget());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MutableBlobStorage::GetBlobImplWhenReady(
 | |
|     const nsACString& aContentType, MutableBlobStorageCallback* aCallback) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aCallback);
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   // GetBlob can be called just once.
 | |
|   MOZ_ASSERT(mStorageState != eClosed);
 | |
|   StorageState previousState = mStorageState;
 | |
|   mStorageState = eClosed;
 | |
| 
 | |
|   if (previousState == eInTemporaryFile) {
 | |
|     if (NS_FAILED(mErrorResult)) {
 | |
|       MOZ_ASSERT(!mActor);
 | |
| 
 | |
|       RefPtr<Runnable> runnable =
 | |
|           new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult);
 | |
|       EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(mActor);
 | |
| 
 | |
|     // 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.
 | |
|     // This Runnable will also close the FD on the I/O thread.
 | |
|     RefPtr<Runnable> runnable = new LastRunnable(this, aContentType, aCallback);
 | |
| 
 | |
|     // If the dispatching fails, we are shutting down and it's fine to do not
 | |
|     // run the callback.
 | |
|     Unused << DispatchToIOThread(runnable.forget());
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we are waiting for the temporary file, it's better to wait...
 | |
|   if (previousState == eWaitingForTemporaryFile) {
 | |
|     mPendingContentType = aContentType;
 | |
|     mPendingCallback = aCallback;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   RefPtr<BlobImpl> blobImpl;
 | |
| 
 | |
|   if (mData) {
 | |
|     blobImpl = new MemoryBlobImpl(mData, mDataLen,
 | |
|                                   NS_ConvertUTF8toUTF16(aContentType));
 | |
| 
 | |
|     mData = nullptr;  // The MemoryBlobImpl takes ownership of the buffer
 | |
|     mDataLen = 0;
 | |
|     mDataBufferLen = 0;
 | |
|   } else {
 | |
|     blobImpl = new EmptyBlobImpl(NS_ConvertUTF8toUTF16(aContentType));
 | |
|   }
 | |
| 
 | |
|   RefPtr<BlobCreationDoneRunnable> runnable =
 | |
|       new BlobCreationDoneRunnable(this, aCallback, blobImpl, NS_OK);
 | |
| 
 | |
|   nsresult error =
 | |
|       EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
 | |
|   if (NS_WARN_IF(NS_FAILED(error))) {
 | |
|     return;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult MutableBlobStorage::Append(const void* aData, uint32_t aLength) {
 | |
|   // This method can be called on any thread.
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   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(lock, aLength) &&
 | |
|       !MaybeCreateTemporaryFile(lock)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // If we are already in the temporaryFile mode, we have to dispatch a
 | |
|   // runnable.
 | |
|   if (mStorageState == eInTemporaryFile) {
 | |
|     // If a previous operation failed, let's return that error now.
 | |
|     if (NS_FAILED(mErrorResult)) {
 | |
|       return mErrorResult;
 | |
|     }
 | |
| 
 | |
|     RefPtr<WriteRunnable> runnable =
 | |
|         WriteRunnable::CopyBuffer(this, aData, aLength);
 | |
|     if (NS_WARN_IF(!runnable)) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
| 
 | |
|     nsresult rv = DispatchToIOThread(runnable.forget());
 | |
|     if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     mDataLen += aLength;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // By default, we store in memory.
 | |
| 
 | |
|   uint64_t offset = mDataLen;
 | |
| 
 | |
|   if (!ExpandBufferSize(lock, aLength)) {
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   memcpy((char*)mData + offset, aData, aLength);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool MutableBlobStorage::ExpandBufferSize(const MutexAutoLock& aProofOfLock,
 | |
|                                           uint64_t aSize) {
 | |
|   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(
 | |
|     const MutexAutoLock& aProofOfLock, uint64_t aSize) const {
 | |
|   MOZ_ASSERT(mStorageState == eInMemory);
 | |
| 
 | |
|   CheckedUint32 bufferSize = mDataLen;
 | |
|   bufferSize += aSize;
 | |
| 
 | |
|   if (!bufferSize.isValid()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return bufferSize.value() >= mMaxMemory;
 | |
| }
 | |
| 
 | |
| bool MutableBlobStorage::MaybeCreateTemporaryFile(
 | |
|     const MutexAutoLock& aProofOfLock) {
 | |
|   mStorageState = eWaitingForTemporaryFile;
 | |
| 
 | |
|   if (!NS_IsMainThread()) {
 | |
|     RefPtr<MutableBlobStorage> self = this;
 | |
|     nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
 | |
|         "MutableBlobStorage::MaybeCreateTemporaryFile", [self]() {
 | |
|           MutexAutoLock lock(self->mMutex);
 | |
|           self->MaybeCreateTemporaryFileOnMainThread(lock);
 | |
|           if (!self->mActor) {
 | |
|             self->ErrorPropagated(NS_ERROR_FAILURE);
 | |
|           }
 | |
|         });
 | |
|     EventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   MaybeCreateTemporaryFileOnMainThread(aProofOfLock);
 | |
|   return !!mActor;
 | |
| }
 | |
| 
 | |
| void MutableBlobStorage::MaybeCreateTemporaryFileOnMainThread(
 | |
|     const MutexAutoLock& aProofOfLock) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(!mActor);
 | |
| 
 | |
|   mozilla::ipc::PBackgroundChild* actorChild =
 | |
|       mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
 | |
|   if (NS_WARN_IF(!actorChild)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   mActor = new TemporaryIPCBlobChild(this);
 | |
|   actorChild->SendPTemporaryIPCBlobConstructor(mActor);
 | |
| 
 | |
|   // We need manually to increase the reference for this actor because the
 | |
|   // IPC allocator method is not triggered. The Release() is called by IPDL
 | |
|   // when the actor is deleted.
 | |
|   mActor.get()->AddRef();
 | |
| 
 | |
|   // The actor will call us when the FileDescriptor is received.
 | |
| }
 | |
| 
 | |
| void MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile ||
 | |
|              mStorageState == eClosed);
 | |
|   MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed);
 | |
|   MOZ_ASSERT(mActor);
 | |
|   MOZ_ASSERT(aFD);
 | |
| 
 | |
|   // 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);
 | |
| 
 | |
|     // If this dispatching fails, CloseFileRunnable will close the FD in the
 | |
|     // DTOR on the current thread.
 | |
|     Unused << DispatchToIOThread(runnable.forget());
 | |
| 
 | |
|     // Let's inform the parent that we have nothing else to do.
 | |
|     mActor->SendOperationFailed();
 | |
|     mActor = nullptr;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we still receiving data, we can proceed in temporary-file mode.
 | |
|   if (mStorageState == eWaitingForTemporaryFile) {
 | |
|     mStorageState = eInTemporaryFile;
 | |
|   }
 | |
| 
 | |
|   mFD = aFD;
 | |
|   MOZ_ASSERT(NS_SUCCEEDED(mErrorResult));
 | |
| 
 | |
|   // This runnable takes the ownership of mData and it will write this buffer
 | |
|   // into the temporary file.
 | |
|   RefPtr<WriteRunnable> runnable =
 | |
|       WriteRunnable::AdoptBuffer(this, mData, mDataLen);
 | |
|   MOZ_ASSERT(runnable);
 | |
| 
 | |
|   mData = nullptr;
 | |
| 
 | |
|   nsresult rv = DispatchToIOThread(runnable.forget());
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     // Shutting down, we cannot continue.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we are closed, it means that GetBlobImplWhenReady() 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, mPendingContentType, mPendingCallback);
 | |
|     Unused << DispatchToIOThread(runnable.forget());
 | |
| 
 | |
|     mPendingCallback = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MutableBlobStorage::AskForBlob(TemporaryIPCBlobChildCallback* aCallback,
 | |
|                                     const nsACString& aContentType) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(mStorageState == eClosed);
 | |
|   MOZ_ASSERT(mFD);
 | |
|   MOZ_ASSERT(mActor);
 | |
|   MOZ_ASSERT(aCallback);
 | |
| 
 | |
|   // Let's pass the FileDescriptor to the parent actor in order to keep the file
 | |
|   // locked on windows.
 | |
|   mActor->AskForBlob(aCallback, aContentType, mFD);
 | |
| 
 | |
|   // The previous operation has duplicated the file descriptor. Now we can close
 | |
|   // mFD. The parent will take care of closing the duplicated file descriptor on
 | |
|   // its side.
 | |
|   RefPtr<Runnable> runnable = new CloseFileRunnable(mFD);
 | |
|   Unused << DispatchToIOThread(runnable.forget());
 | |
| 
 | |
|   mFD = nullptr;
 | |
|   mActor = nullptr;
 | |
| }
 | |
| 
 | |
| void MutableBlobStorage::ErrorPropagated(nsresult aRv) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   mErrorResult = aRv;
 | |
| 
 | |
|   if (mActor) {
 | |
|     mActor->SendOperationFailed();
 | |
|     mActor = nullptr;
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult 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);
 | |
|   nsresult rv = mTaskQueue->Dispatch(runnable.forget());
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| size_t MutableBlobStorage::SizeOfCurrentMemoryBuffer() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mStorageState < eInTemporaryFile ? mDataLen : 0;
 | |
| }
 | |
| 
 | |
| PRFileDesc* MutableBlobStorage::GetFD() {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mFD;
 | |
| }
 | |
| 
 | |
| void MutableBlobStorage::CloseFD() {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(mFD);
 | |
| 
 | |
|   PR_Close(mFD);
 | |
|   mFD = nullptr;
 | |
| }
 | |
| 
 | |
| }  // namespace dom
 | |
| }  // namespace mozilla
 |