summaryrefslogtreecommitdiffstats
path: root/dom/file/MutableBlobStorage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/file/MutableBlobStorage.cpp')
-rw-r--r--dom/file/MutableBlobStorage.cpp667
1 files changed, 667 insertions, 0 deletions
diff --git a/dom/file/MutableBlobStorage.cpp b/dom/file/MutableBlobStorage.cpp
new file mode 100644
index 0000000000..75d0dce6de
--- /dev/null
+++ b/dom/file/MutableBlobStorage.cpp
@@ -0,0 +1,667 @@
+/* -*- 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::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() override {
+ 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() override { 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() override {
+ 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() override {
+ 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() override {
+ 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 = GetMainThreadSerialEventTarget();
+ }
+
+ 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);
+ (void)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.
+ (void)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.
+ (void)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);
+ (void)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);
+ (void)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 = TaskQueue::Create(target.forget(), "BlobStorage");
+ }
+
+ 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 mozilla::dom