summaryrefslogtreecommitdiffstats
path: root/dom/filehandle
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/filehandle/ActorsParent.cpp2190
-rw-r--r--dom/filehandle/ActorsParent.h174
-rw-r--r--dom/filehandle/FileHandleStorage.h21
-rw-r--r--dom/filehandle/PBackgroundFileHandle.ipdl90
-rw-r--r--dom/filehandle/PBackgroundFileRequest.ipdl65
-rw-r--r--dom/filehandle/PBackgroundMutableFile.ipdl39
-rw-r--r--dom/filehandle/SerializationHelpers.h24
-rw-r--r--dom/filehandle/moz.build35
8 files changed, 2638 insertions, 0 deletions
diff --git a/dom/filehandle/ActorsParent.cpp b/dom/filehandle/ActorsParent.cpp
new file mode 100644
index 0000000000..6338d5b1d4
--- /dev/null
+++ b/dom/filehandle/ActorsParent.cpp
@@ -0,0 +1,2190 @@
+/* -*- 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 "ActorsParent.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FixedBufferOutputStream.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/PBackgroundFileHandleParent.h"
+#include "mozilla/dom/PBackgroundFileRequestParent.h"
+#include "mozilla/dom/indexedDB/ActorsParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsIEventTarget.h"
+#include "nsIFile.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIRunnable.h"
+#include "nsISeekableStream.h"
+#include "nsIThread.h"
+#include "nsIThreadPool.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsTArray.h"
+#include "nsThreadPool.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+
+namespace mozilla::dom {
+
+using namespace mozilla::dom::quota;
+using namespace mozilla::ipc;
+
+namespace {
+
+/******************************************************************************
+ * Constants
+ ******************************************************************************/
+
+const uint32_t kThreadLimit = 5;
+const uint32_t kIdleThreadLimit = 1;
+const uint32_t kIdleThreadTimeoutMs = 30000;
+
+const uint32_t kStreamCopyBlockSize = 32768;
+
+} // namespace
+
+class FileHandleThreadPool::FileHandleQueue final : public Runnable {
+ friend class FileHandleThreadPool;
+
+ RefPtr<FileHandleThreadPool> mOwningFileHandleThreadPool;
+ RefPtr<FileHandle> mFileHandle;
+ nsTArray<RefPtr<FileHandleOp>> mQueue;
+ RefPtr<FileHandleOp> mCurrentOp;
+ bool mShouldFinish;
+
+ public:
+ explicit FileHandleQueue(FileHandleThreadPool* aFileHandleThreadPool,
+ FileHandle* aFileHandle);
+
+ void Enqueue(FileHandleOp* aFileHandleOp);
+
+ void Finish();
+
+ void ProcessQueue();
+
+ private:
+ ~FileHandleQueue() = default;
+
+ NS_DECL_NSIRUNNABLE
+};
+
+struct FileHandleThreadPool::DelayedEnqueueInfo {
+ RefPtr<FileHandle> mFileHandle;
+ RefPtr<FileHandleOp> mFileHandleOp;
+ bool mFinish;
+};
+
+class FileHandleThreadPool::DirectoryInfo {
+ friend class FileHandleThreadPool;
+
+ RefPtr<FileHandleThreadPool> mOwningFileHandleThreadPool;
+ nsTArray<RefPtr<FileHandleQueue>> mFileHandleQueues;
+ nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
+ nsTHashSet<nsString> mFilesReading;
+ nsTHashSet<nsString> mFilesWriting;
+
+ public:
+ FileHandleQueue* CreateFileHandleQueue(FileHandle* aFileHandle);
+
+ FileHandleQueue* GetFileHandleQueue(FileHandle* aFileHandle);
+
+ void RemoveFileHandleQueue(FileHandle* aFileHandle);
+
+ bool HasRunningFileHandles() { return !mFileHandleQueues.IsEmpty(); }
+
+ DelayedEnqueueInfo* CreateDelayedEnqueueInfo(FileHandle* aFileHandle,
+ FileHandleOp* aFileHandleOp,
+ bool aFinish);
+
+ void LockFileForReading(const nsAString& aFileName) {
+ mFilesReading.Insert(aFileName);
+ }
+
+ void LockFileForWriting(const nsAString& aFileName) {
+ mFilesWriting.Insert(aFileName);
+ }
+
+ bool IsFileLockedForReading(const nsAString& aFileName) {
+ return mFilesReading.Contains(aFileName);
+ }
+
+ bool IsFileLockedForWriting(const nsAString& aFileName) {
+ return mFilesWriting.Contains(aFileName);
+ }
+
+ private:
+ explicit DirectoryInfo(FileHandleThreadPool* aFileHandleThreadPool)
+ : mOwningFileHandleThreadPool(aFileHandleThreadPool) {}
+};
+
+struct FileHandleThreadPool::StoragesCompleteCallback final {
+ friend class DefaultDelete<StoragesCompleteCallback>;
+
+ nsTArray<nsCString> mDirectoryIds;
+ nsCOMPtr<nsIRunnable> mCallback;
+
+ StoragesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
+ nsIRunnable* aCallback);
+
+ private:
+ ~StoragesCompleteCallback();
+};
+
+/******************************************************************************
+ * Actor class declarations
+ ******************************************************************************/
+
+class FileHandle : public PBackgroundFileHandleParent {
+ friend class BackgroundMutableFileParentBase;
+
+ class FinishOp;
+
+ RefPtr<BackgroundMutableFileParentBase> mMutableFile;
+ nsCOMPtr<nsISupports> mStream;
+ uint64_t mActiveRequestCount;
+ FileHandleStorage mStorage;
+ Atomic<bool> mInvalidatedOnAnyThread;
+ FileMode mMode;
+ bool mHasBeenActive;
+ bool mActorDestroyed;
+ bool mInvalidated;
+ bool mAborted;
+ bool mFinishOrAbortReceived;
+ bool mFinishedOrAborted;
+ bool mForceAborted;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIEventTarget> mThreadPoolEventTarget;
+#endif
+
+ public:
+ void AssertIsOnThreadPool() const;
+
+ bool IsActorDestroyed() const {
+ AssertIsOnBackgroundThread();
+
+ return mActorDestroyed;
+ }
+
+ // Must be called on the background thread.
+ bool IsInvalidated() const {
+ MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
+ MOZ_ASSERT_IF(mInvalidated, mAborted);
+
+ return mInvalidated;
+ }
+
+ // May be called on any thread, but is more expensive than IsInvalidated().
+ bool IsInvalidatedOnAnyThread() const { return mInvalidatedOnAnyThread; }
+
+ void SetActive() {
+ AssertIsOnBackgroundThread();
+
+ mHasBeenActive = true;
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::FileHandle)
+
+ nsresult GetOrCreateStream(nsISupports** aStream);
+
+ void Abort(bool aForce);
+
+ FileHandleStorage Storage() const { return mStorage; }
+
+ FileMode Mode() const { return mMode; }
+
+ BackgroundMutableFileParentBase* GetMutableFile() const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mMutableFile);
+
+ return mMutableFile;
+ }
+
+ bool IsAborted() const {
+ AssertIsOnBackgroundThread();
+
+ return mAborted;
+ }
+
+ PBackgroundParent* GetBackgroundParent() const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ return GetMutableFile()->GetBackgroundParent();
+ }
+
+ void NoteActiveRequest();
+
+ void NoteFinishedRequest();
+
+ void Invalidate();
+
+ private:
+ // This constructor is only called by BackgroundMutableFileParentBase.
+ FileHandle(BackgroundMutableFileParentBase* aMutableFile, FileMode aMode);
+
+ // Reference counted.
+ ~FileHandle();
+
+ void MaybeFinishOrAbort() {
+ AssertIsOnBackgroundThread();
+
+ // If we've already finished or aborted then there's nothing else to do.
+ if (mFinishedOrAborted) {
+ return;
+ }
+
+ // If there are active requests then we have to wait for those requests to
+ // complete (see NoteFinishedRequest).
+ if (mActiveRequestCount) {
+ return;
+ }
+
+ // If we haven't yet received a finish or abort message then there could be
+ // additional requests coming so we should wait unless we're being forced to
+ // abort.
+ if (!mFinishOrAbortReceived && !mForceAborted) {
+ return;
+ }
+
+ FinishOrAbort();
+ }
+
+ void SendCompleteNotification(bool aAborted);
+
+ bool VerifyRequestParams(const FileRequestParams& aParams) const;
+
+ bool VerifyRequestData(const FileRequestData& aData) const;
+
+ void FinishOrAbort();
+
+ // IPDL methods are only called by IPDL.
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual mozilla::ipc::IPCResult RecvDeleteMe() override;
+
+ virtual mozilla::ipc::IPCResult RecvFinish() override;
+
+ virtual mozilla::ipc::IPCResult RecvAbort() override;
+
+ virtual PBackgroundFileRequestParent* AllocPBackgroundFileRequestParent(
+ const FileRequestParams& aParams) override;
+
+ virtual mozilla::ipc::IPCResult RecvPBackgroundFileRequestConstructor(
+ PBackgroundFileRequestParent* aActor,
+ const FileRequestParams& aParams) override;
+
+ virtual bool DeallocPBackgroundFileRequestParent(
+ PBackgroundFileRequestParent* aActor) override;
+};
+
+class FileHandleOp {
+ protected:
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+ RefPtr<FileHandle> mFileHandle;
+#ifdef DEBUG
+ bool mEnqueued;
+#endif
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileHandleOp)
+
+ void AssertIsOnOwningThread() const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOwningEventTarget);
+ DebugOnly<bool> current;
+ MOZ_ASSERT(NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+ }
+
+ nsIEventTarget* OwningThread() const { return mOwningEventTarget; }
+
+ void AssertIsOnThreadPool() const {
+ MOZ_ASSERT(mFileHandle);
+ mFileHandle->AssertIsOnThreadPool();
+ }
+
+ void Enqueue();
+
+ virtual void RunOnThreadPool() = 0;
+
+ virtual void RunOnOwningThread() = 0;
+
+ protected:
+ FileHandleOp(FileHandle* aFileHandle)
+ : mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mFileHandle(aFileHandle)
+#ifdef DEBUG
+ ,
+ mEnqueued(false)
+#endif
+ {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+ }
+
+ virtual ~FileHandleOp() = default;
+};
+
+class FileHandle::FinishOp : public FileHandleOp {
+ friend class FileHandle;
+
+ bool mAborted;
+
+ private:
+ FinishOp(FileHandle* aFileHandle, bool aAborted)
+ : FileHandleOp(aFileHandle), mAborted(aAborted) {
+ MOZ_ASSERT(aFileHandle);
+ }
+
+ ~FinishOp() = default;
+
+ virtual void RunOnThreadPool() override;
+
+ virtual void RunOnOwningThread() override;
+};
+
+class NormalFileHandleOp : public FileHandleOp,
+ public PBackgroundFileRequestParent {
+ nsresult mResultCode;
+ Atomic<bool> mOperationMayProceed;
+ bool mActorDestroyed;
+ const bool mFileHandleIsAborted;
+
+#ifdef DEBUG
+ bool mResponseSent;
+#endif
+
+ protected:
+ nsCOMPtr<nsISupports> mFileStream;
+
+ public:
+ void NoteActorDestroyed() {
+ AssertIsOnOwningThread();
+
+ mActorDestroyed = true;
+ mOperationMayProceed = false;
+ }
+
+ bool IsActorDestroyed() const {
+ AssertIsOnOwningThread();
+
+ return mActorDestroyed;
+ }
+
+ // May be called on any thread, but you should call IsActorDestroyed() if
+ // you know you're on the background thread because it is slightly faster.
+ bool OperationMayProceed() const { return mOperationMayProceed; }
+
+ // May be overridden by subclasses if they need to perform work on the
+ // background thread before being enqueued. Returning false will kill the
+ // child actors and prevent enqueue.
+ virtual bool Init(FileHandle* aFileHandle);
+
+ // This callback will be called on the background thread before releasing the
+ // final reference to this request object. Subclasses may perform any
+ // additional cleanup here but must always call the base class implementation.
+ virtual void Cleanup();
+
+ protected:
+ NormalFileHandleOp(FileHandle* aFileHandle)
+ : FileHandleOp(aFileHandle),
+ mResultCode(NS_OK),
+ mOperationMayProceed(true),
+ mActorDestroyed(false),
+ mFileHandleIsAborted(aFileHandle->IsAborted())
+#ifdef DEBUG
+ ,
+ mResponseSent(false)
+#endif
+ {
+ MOZ_ASSERT(aFileHandle);
+ }
+
+ virtual ~NormalFileHandleOp();
+
+ // Must be overridden in subclasses. Called on the target thread to allow the
+ // subclass to perform necessary file operations. A successful return value
+ // will trigger a SendSuccessResult callback on the background thread while
+ // a failure value will trigger a SendFailureResult callback.
+ virtual nsresult DoFileWork(FileHandle* aFileHandle) = 0;
+
+ // Subclasses use this override to set the IPDL response value.
+ virtual void GetResponse(FileRequestResponse& aResponse) = 0;
+
+ private:
+ nsresult SendSuccessResult();
+
+ bool SendFailureResult(nsresult aResultCode);
+
+ virtual void RunOnThreadPool() override;
+
+ virtual void RunOnOwningThread() override;
+
+ // IPDL methods.
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+};
+
+class CopyFileHandleOp : public NormalFileHandleOp {
+ class ProgressRunnable;
+
+ protected:
+ nsCOMPtr<nsISupports> mBufferStream;
+
+ uint64_t mOffset;
+ uint64_t mSize;
+
+ bool mRead;
+
+ protected:
+ CopyFileHandleOp(FileHandle* aFileHandle)
+ : NormalFileHandleOp(aFileHandle), mOffset(0), mSize(0), mRead(true) {}
+
+ virtual nsresult DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void Cleanup() override;
+};
+
+class CopyFileHandleOp::ProgressRunnable final : public Runnable {
+ RefPtr<CopyFileHandleOp> mCopyFileHandleOp;
+ uint64_t mProgress;
+ uint64_t mProgressMax;
+
+ public:
+ ProgressRunnable(CopyFileHandleOp* aCopyFileHandleOp, uint64_t aProgress,
+ uint64_t aProgressMax)
+ : Runnable("dom::CopyFileHandleOp::ProgressRunnable"),
+ mCopyFileHandleOp(aCopyFileHandleOp),
+ mProgress(aProgress),
+ mProgressMax(aProgressMax) {}
+
+ private:
+ ~ProgressRunnable() = default;
+
+ NS_DECL_NSIRUNNABLE
+};
+
+class GetMetadataOp : public NormalFileHandleOp {
+ friend class FileHandle;
+
+ const FileRequestGetMetadataParams mParams;
+
+ protected:
+ FileRequestMetadata mMetadata;
+
+ protected:
+ // Only created by FileHandle.
+ GetMetadataOp(FileHandle* aFileHandle, const FileRequestParams& aParams);
+
+ ~GetMetadataOp() = default;
+
+ virtual nsresult DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class ReadOp final : public CopyFileHandleOp {
+ friend class FileHandle;
+
+ const FileRequestReadParams mParams;
+
+ private:
+ // Only created by FileHandle.
+ ReadOp(FileHandle* aFileHandle, const FileRequestParams& aParams);
+
+ ~ReadOp() = default;
+
+ virtual bool Init(FileHandle* aFileHandle) override;
+
+ virtual void GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class WriteOp final : public CopyFileHandleOp {
+ friend class FileHandle;
+
+ const FileRequestWriteParams mParams;
+
+ private:
+ // Only created by FileHandle.
+ WriteOp(FileHandle* aFileHandle, const FileRequestParams& aParams);
+
+ ~WriteOp() = default;
+
+ virtual bool Init(FileHandle* aFileHandle) override;
+
+ virtual void GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class TruncateOp final : public NormalFileHandleOp {
+ friend class FileHandle;
+
+ const FileRequestTruncateParams mParams;
+
+ private:
+ // Only created by FileHandle.
+ TruncateOp(FileHandle* aFileHandle, const FileRequestParams& aParams);
+
+ ~TruncateOp() = default;
+
+ virtual nsresult DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void GetResponse(FileRequestResponse& aResponse) override;
+};
+
+class FlushOp final : public NormalFileHandleOp {
+ friend class FileHandle;
+
+ const FileRequestFlushParams mParams;
+
+ private:
+ // Only created by FileHandle.
+ FlushOp(FileHandle* aFileHandle, const FileRequestParams& aParams);
+
+ ~FlushOp() = default;
+
+ virtual nsresult DoFileWork(FileHandle* aFileHandle) override;
+
+ virtual void GetResponse(FileRequestResponse& aResponse) override;
+};
+
+namespace {
+
+/*******************************************************************************
+ * Helper Functions
+ ******************************************************************************/
+
+FileHandleThreadPool* GetFileHandleThreadPoolFor(FileHandleStorage aStorage) {
+ switch (aStorage) {
+ case FILE_HANDLE_STORAGE_IDB:
+ return mozilla::dom::indexedDB::GetFileHandleThreadPool();
+
+ default:
+ MOZ_CRASH("Bad file handle storage value!");
+ }
+}
+
+nsresult ClampResultCode(nsresult aResultCode) {
+ if (NS_SUCCEEDED(aResultCode) ||
+ NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_FILEHANDLE) {
+ return aResultCode;
+ }
+
+ NS_WARNING(nsPrintfCString("Converting non-filehandle error code (0x%" PRIX32
+ ") to "
+ "NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR",
+ static_cast<uint32_t>(aResultCode))
+ .get());
+
+ return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+}
+
+} // namespace
+
+/*******************************************************************************
+ * FileHandleThreadPool implementation
+ ******************************************************************************/
+
+FileHandleThreadPool::FileHandleThreadPool()
+ : mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mShutdownRequested(false),
+ mShutdownComplete(false) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mOwningEventTarget);
+ AssertIsOnOwningThread();
+}
+
+FileHandleThreadPool::~FileHandleThreadPool() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mDirectoryInfos.Count());
+ MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+ MOZ_ASSERT(mShutdownRequested);
+ MOZ_ASSERT(mShutdownComplete);
+}
+
+// static
+already_AddRefed<FileHandleThreadPool> FileHandleThreadPool::Create() {
+ AssertIsOnBackgroundThread();
+
+ RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+ new FileHandleThreadPool();
+ fileHandleThreadPool->AssertIsOnOwningThread();
+
+ if (NS_WARN_IF(NS_FAILED(fileHandleThreadPool->Init()))) {
+ return nullptr;
+ }
+
+ return fileHandleThreadPool.forget();
+}
+
+#ifdef DEBUG
+
+void FileHandleThreadPool::AssertIsOnOwningThread() const {
+ MOZ_ASSERT(mOwningEventTarget);
+
+ bool current;
+ MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->IsOnCurrentThread(&current));
+ MOZ_ASSERT(current);
+}
+
+nsIEventTarget* FileHandleThreadPool::GetThreadPoolEventTarget() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mThreadPool);
+
+ return mThreadPool;
+}
+
+#endif // DEBUG
+
+void FileHandleThreadPool::Enqueue(FileHandle* aFileHandle,
+ FileHandleOp* aFileHandleOp, bool aFinish) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(!mShutdownRequested);
+
+ BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+
+ const nsACString& directoryId = mutableFile->DirectoryId();
+ const nsAString& fileName = mutableFile->FileName();
+ bool modeIsWrite = aFileHandle->Mode() == FileMode::Readwrite;
+
+ DirectoryInfo* directoryInfo =
+ mDirectoryInfos
+ .LookupOrInsertWith(
+ directoryId,
+ [&] { return UniquePtr<DirectoryInfo>(new DirectoryInfo(this)); })
+ .get();
+
+ FileHandleQueue* existingFileHandleQueue =
+ directoryInfo->GetFileHandleQueue(aFileHandle);
+
+ if (existingFileHandleQueue) {
+ existingFileHandleQueue->Enqueue(aFileHandleOp);
+ if (aFinish) {
+ existingFileHandleQueue->Finish();
+ }
+ return;
+ }
+
+ bool lockedForReading = directoryInfo->IsFileLockedForReading(fileName);
+ bool lockedForWriting = directoryInfo->IsFileLockedForWriting(fileName);
+
+ if (modeIsWrite) {
+ if (!lockedForWriting) {
+ directoryInfo->LockFileForWriting(fileName);
+ }
+ } else {
+ if (!lockedForReading) {
+ directoryInfo->LockFileForReading(fileName);
+ }
+ }
+
+ if (lockedForWriting || (lockedForReading && modeIsWrite)) {
+ directoryInfo->CreateDelayedEnqueueInfo(aFileHandle, aFileHandleOp,
+ aFinish);
+ } else {
+ FileHandleQueue* fileHandleQueue =
+ directoryInfo->CreateFileHandleQueue(aFileHandle);
+
+ if (aFileHandleOp) {
+ fileHandleQueue->Enqueue(aFileHandleOp);
+ if (aFinish) {
+ fileHandleQueue->Finish();
+ }
+ }
+ }
+}
+
+void FileHandleThreadPool::WaitForDirectoriesToComplete(
+ nsTArray<nsCString>&& aDirectoryIds, nsIRunnable* aCallback) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!aDirectoryIds.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ auto callback =
+ MakeUnique<StoragesCompleteCallback>(std::move(aDirectoryIds), aCallback);
+
+ if (!MaybeFireCallback(callback.get())) {
+ mCompleteCallbacks.AppendElement(std::move(callback));
+ }
+}
+
+void FileHandleThreadPool::Shutdown() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(!mShutdownRequested);
+ MOZ_ASSERT(!mShutdownComplete);
+
+ mShutdownRequested = true;
+
+ if (!mThreadPool) {
+ MOZ_ASSERT(!mDirectoryInfos.Count());
+ MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
+
+ mShutdownComplete = true;
+ return;
+ }
+
+ if (!mDirectoryInfos.Count()) {
+ Cleanup();
+
+ MOZ_ASSERT(mShutdownComplete);
+ return;
+ }
+
+ MOZ_ALWAYS_TRUE(SpinEventLoopUntil("FileHandleThreadPool::Shutdown"_ns,
+ [&]() { return mShutdownComplete; }));
+}
+
+nsresult FileHandleThreadPool::Init() {
+ AssertIsOnOwningThread();
+
+ mThreadPool = new nsThreadPool();
+
+ nsresult rv = mThreadPool->SetName("FileHandles"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mThreadPool->SetThreadLimit(kThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void FileHandleThreadPool::Cleanup() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mThreadPool);
+ MOZ_ASSERT(mShutdownRequested);
+ MOZ_ASSERT(!mShutdownComplete);
+ MOZ_ASSERT(!mDirectoryInfos.Count());
+
+ MOZ_ALWAYS_SUCCEEDS(mThreadPool->Shutdown());
+
+ if (!mCompleteCallbacks.IsEmpty()) {
+ // Run all callbacks manually now.
+ for (uint32_t count = mCompleteCallbacks.Length(), index = 0; index < count;
+ index++) {
+ UniquePtr<StoragesCompleteCallback> completeCallback =
+ std::move(mCompleteCallbacks[index]);
+ MOZ_ASSERT(completeCallback);
+ MOZ_ASSERT(completeCallback->mCallback);
+
+ Unused << completeCallback->mCallback->Run();
+ }
+
+ mCompleteCallbacks.Clear();
+
+ // And make sure they get processed.
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread));
+ }
+
+ mShutdownComplete = true;
+}
+
+void FileHandleThreadPool::FinishFileHandle(FileHandle* aFileHandle) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+ const nsACString& directoryId = mutableFile->DirectoryId();
+
+ DirectoryInfo* directoryInfo;
+ if (!mDirectoryInfos.Get(directoryId, &directoryInfo)) {
+ NS_ERROR("We don't know anyting about this directory?!");
+ return;
+ }
+
+ directoryInfo->RemoveFileHandleQueue(aFileHandle);
+
+ if (!directoryInfo->HasRunningFileHandles()) {
+ mDirectoryInfos.Remove(directoryId);
+
+ // See if we need to fire any complete callbacks.
+ mCompleteCallbacks.RemoveElementsBy(
+ [this](const UniquePtr<StoragesCompleteCallback>& callback) {
+ return MaybeFireCallback(callback.get());
+ });
+
+ if (mShutdownRequested && !mDirectoryInfos.Count()) {
+ Cleanup();
+ }
+ }
+}
+
+bool FileHandleThreadPool::MaybeFireCallback(
+ StoragesCompleteCallback* aCallback) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aCallback);
+ MOZ_ASSERT(!aCallback->mDirectoryIds.IsEmpty());
+ MOZ_ASSERT(aCallback->mCallback);
+
+ for (uint32_t count = aCallback->mDirectoryIds.Length(), index = 0;
+ index < count; index++) {
+ const nsCString& directoryId = aCallback->mDirectoryIds[index];
+ MOZ_ASSERT(!directoryId.IsEmpty());
+
+ if (mDirectoryInfos.Get(directoryId, nullptr)) {
+ return false;
+ }
+ }
+
+ aCallback->mCallback->Run();
+ return true;
+}
+
+FileHandleThreadPool::FileHandleQueue::FileHandleQueue(
+ FileHandleThreadPool* aFileHandleThreadPool, FileHandle* aFileHandle)
+ : Runnable("dom::FileHandleThreadPool::FileHandleQueue"),
+ mOwningFileHandleThreadPool(aFileHandleThreadPool),
+ mFileHandle(aFileHandle),
+ mShouldFinish(false) {
+ MOZ_ASSERT(aFileHandleThreadPool);
+ aFileHandleThreadPool->AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+}
+
+void FileHandleThreadPool::FileHandleQueue::Enqueue(
+ FileHandleOp* aFileHandleOp) {
+ MOZ_ASSERT(!mShouldFinish, "Enqueue called after Finish!");
+
+ mQueue.AppendElement(aFileHandleOp);
+
+ ProcessQueue();
+}
+
+void FileHandleThreadPool::FileHandleQueue::Finish() {
+ MOZ_ASSERT(!mShouldFinish, "Finish called more than once!");
+
+ mShouldFinish = true;
+}
+
+void FileHandleThreadPool::FileHandleQueue::ProcessQueue() {
+ if (mCurrentOp) {
+ return;
+ }
+
+ if (mQueue.IsEmpty()) {
+ if (mShouldFinish) {
+ mOwningFileHandleThreadPool->FinishFileHandle(mFileHandle);
+
+ // Make sure this is released on this thread.
+ mOwningFileHandleThreadPool = nullptr;
+ }
+
+ return;
+ }
+
+ mCurrentOp = mQueue[0];
+ mQueue.RemoveElementAt(0);
+
+ nsCOMPtr<nsIThreadPool> threadPool = mOwningFileHandleThreadPool->mThreadPool;
+ MOZ_ASSERT(threadPool);
+
+ MOZ_ALWAYS_SUCCEEDS(threadPool->Dispatch(this, NS_DISPATCH_NORMAL));
+}
+
+NS_IMETHODIMP
+FileHandleThreadPool::FileHandleQueue::Run() {
+ MOZ_ASSERT(mCurrentOp);
+
+ if (IsOnBackgroundThread()) {
+ RefPtr<FileHandleOp> currentOp;
+
+ mCurrentOp.swap(currentOp);
+ ProcessQueue();
+
+ currentOp->RunOnOwningThread();
+ } else {
+ mCurrentOp->RunOnThreadPool();
+
+ nsCOMPtr<nsIEventTarget> backgroundThread = mCurrentOp->OwningThread();
+
+ MOZ_ALWAYS_SUCCEEDS(backgroundThread->Dispatch(this, NS_DISPATCH_NORMAL));
+ }
+
+ return NS_OK;
+}
+
+auto FileHandleThreadPool::DirectoryInfo::CreateFileHandleQueue(
+ FileHandle* aFileHandle) -> FileHandleQueue* {
+ RefPtr<FileHandleQueue>* fileHandleQueue = mFileHandleQueues.AppendElement();
+ *fileHandleQueue =
+ new FileHandleQueue(mOwningFileHandleThreadPool, aFileHandle);
+ return fileHandleQueue->get();
+}
+
+auto FileHandleThreadPool::DirectoryInfo::GetFileHandleQueue(
+ FileHandle* aFileHandle) -> FileHandleQueue* {
+ uint32_t count = mFileHandleQueues.Length();
+ for (uint32_t index = 0; index < count; index++) {
+ RefPtr<FileHandleQueue>& fileHandleQueue = mFileHandleQueues[index];
+ if (fileHandleQueue->mFileHandle == aFileHandle) {
+ return fileHandleQueue;
+ }
+ }
+ return nullptr;
+}
+
+void FileHandleThreadPool::DirectoryInfo::RemoveFileHandleQueue(
+ FileHandle* aFileHandle) {
+ for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
+ if (mDelayedEnqueueInfos[index].mFileHandle == aFileHandle) {
+ MOZ_ASSERT(!mDelayedEnqueueInfos[index].mFileHandleOp, "Should be null!");
+ mDelayedEnqueueInfos.RemoveElementAt(index);
+ return;
+ }
+ }
+
+ uint32_t fileHandleCount = mFileHandleQueues.Length();
+
+ // We can't just remove entries from lock hash tables, we have to rebuild
+ // them instead. Multiple FileHandle objects may lock the same file
+ // (one entry can represent multiple locks).
+
+ mFilesReading.Clear();
+ mFilesWriting.Clear();
+
+ for (uint32_t index = 0, count = fileHandleCount; index < count; index++) {
+ FileHandle* fileHandle = mFileHandleQueues[index]->mFileHandle;
+ if (fileHandle == aFileHandle) {
+ MOZ_ASSERT(count == fileHandleCount, "More than one match?!");
+
+ mFileHandleQueues.RemoveElementAt(index);
+ index--;
+ count--;
+
+ continue;
+ }
+
+ const nsAString& fileName = fileHandle->GetMutableFile()->FileName();
+
+ if (fileHandle->Mode() == FileMode::Readwrite) {
+ if (!IsFileLockedForWriting(fileName)) {
+ LockFileForWriting(fileName);
+ }
+ } else {
+ if (!IsFileLockedForReading(fileName)) {
+ LockFileForReading(fileName);
+ }
+ }
+ }
+
+ MOZ_ASSERT(mFileHandleQueues.Length() == fileHandleCount - 1,
+ "Didn't find the file handle we were looking for!");
+
+ nsTArray<DelayedEnqueueInfo> delayedEnqueueInfos =
+ std::move(mDelayedEnqueueInfos);
+
+ for (uint32_t index = 0; index < delayedEnqueueInfos.Length(); index++) {
+ DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index];
+ mOwningFileHandleThreadPool->Enqueue(delayedEnqueueInfo.mFileHandle,
+ delayedEnqueueInfo.mFileHandleOp,
+ delayedEnqueueInfo.mFinish);
+ }
+}
+
+auto FileHandleThreadPool::DirectoryInfo::CreateDelayedEnqueueInfo(
+ FileHandle* aFileHandle, FileHandleOp* aFileHandleOp, bool aFinish)
+ -> DelayedEnqueueInfo* {
+ DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
+ info->mFileHandle = aFileHandle;
+ info->mFileHandleOp = aFileHandleOp;
+ info->mFinish = aFinish;
+ return info;
+}
+
+FileHandleThreadPool::StoragesCompleteCallback::StoragesCompleteCallback(
+ nsTArray<nsCString>&& aDirectoryIds, nsIRunnable* aCallback)
+ : mDirectoryIds(std::move(aDirectoryIds)), mCallback(aCallback) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mDirectoryIds.IsEmpty());
+ MOZ_ASSERT(aCallback);
+
+ MOZ_COUNT_CTOR(FileHandleThreadPool::StoragesCompleteCallback);
+}
+
+FileHandleThreadPool::StoragesCompleteCallback::~StoragesCompleteCallback() {
+ AssertIsOnBackgroundThread();
+
+ MOZ_COUNT_DTOR(FileHandleThreadPool::StoragesCompleteCallback);
+}
+
+/*******************************************************************************
+ * BackgroundMutableFileParentBase
+ ******************************************************************************/
+
+BackgroundMutableFileParentBase::BackgroundMutableFileParentBase(
+ FileHandleStorage aStorage, const nsACString& aDirectoryId,
+ const nsAString& aFileName, nsIFile* aFile)
+ : mDirectoryId(aDirectoryId),
+ mFileName(aFileName),
+ mStorage(aStorage),
+ mInvalidated(false),
+ mActorWasAlive(false),
+ mActorDestroyed(false),
+ mFile(aFile) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aStorage != FILE_HANDLE_STORAGE_MAX);
+ MOZ_ASSERT(!aDirectoryId.IsEmpty());
+ MOZ_ASSERT(!aFileName.IsEmpty());
+ MOZ_ASSERT(aFile);
+}
+
+BackgroundMutableFileParentBase::~BackgroundMutableFileParentBase() {
+ MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
+}
+
+void BackgroundMutableFileParentBase::Invalidate() {
+ AssertIsOnBackgroundThread();
+
+ class MOZ_STACK_CLASS Helper final {
+ public:
+ static bool InvalidateFileHandles(nsTHashSet<FileHandle*>& aTable) {
+ AssertIsOnBackgroundThread();
+
+ const uint32_t count = aTable.Count();
+ if (!count) {
+ return true;
+ }
+
+ nsTArray<RefPtr<FileHandle>> fileHandles;
+ if (NS_WARN_IF(!fileHandles.SetCapacity(count, fallible))) {
+ return false;
+ }
+
+ // This can't fail, since we already reserved the required capacity.
+ std::copy(aTable.cbegin(), aTable.cend(), MakeBackInserter(fileHandles));
+
+ for (uint32_t index = 0; index < count; index++) {
+ RefPtr<FileHandle> fileHandle = std::move(fileHandles[index]);
+ MOZ_ASSERT(fileHandle);
+
+ fileHandle->Invalidate();
+ }
+
+ return true;
+ }
+ };
+
+ if (mInvalidated) {
+ return;
+ }
+
+ mInvalidated = true;
+
+ if (!Helper::InvalidateFileHandles(mFileHandles)) {
+ NS_WARNING("Failed to abort all file handles!");
+ }
+}
+
+bool BackgroundMutableFileParentBase::RegisterFileHandle(
+ FileHandle* aFileHandle) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(!mFileHandles.Contains(aFileHandle));
+ MOZ_ASSERT(!mInvalidated);
+
+ if (NS_WARN_IF(!mFileHandles.Insert(aFileHandle, fallible))) {
+ return false;
+ }
+
+ if (mFileHandles.Count() == 1) {
+ NoteActiveState();
+ }
+
+ return true;
+}
+
+void BackgroundMutableFileParentBase::UnregisterFileHandle(
+ FileHandle* aFileHandle) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(mFileHandles.Contains(aFileHandle));
+
+ mFileHandles.Remove(aFileHandle);
+
+ if (!mFileHandles.Count()) {
+ NoteInactiveState();
+ }
+}
+
+void BackgroundMutableFileParentBase::SetActorAlive() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorWasAlive);
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorWasAlive = true;
+
+ // This reference will be absorbed by IPDL and released when the actor is
+ // destroyed.
+ AddRef();
+}
+
+already_AddRefed<nsISupports> BackgroundMutableFileParentBase::CreateStream(
+ bool aReadOnly) {
+ AssertIsOnBackgroundThread();
+
+ nsresult rv;
+
+ if (aReadOnly) {
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), mFile, -1, -1,
+ nsIFileInputStream::DEFER_OPEN);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return stream.forget();
+ }
+
+ nsCOMPtr<nsIRandomAccessStream> stream;
+ rv = NS_NewLocalFileRandomAccessStream(getter_AddRefs(stream), mFile, -1, -1,
+ nsIFileRandomAccessStream::DEFER_OPEN);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+ return stream.forget();
+}
+
+void BackgroundMutableFileParentBase::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ if (!IsInvalidated()) {
+ Invalidate();
+ }
+}
+
+PBackgroundFileHandleParent*
+BackgroundMutableFileParentBase::AllocPBackgroundFileHandleParent(
+ const FileMode& aMode) {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(aMode != FileMode::Readonly && aMode != FileMode::Readwrite)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<FileHandle> fileHandle = new FileHandle(this, aMode);
+
+ return fileHandle.forget().take();
+}
+
+mozilla::ipc::IPCResult
+BackgroundMutableFileParentBase::RecvPBackgroundFileHandleConstructor(
+ PBackgroundFileHandleParent* aActor, const FileMode& aMode) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aMode == FileMode::Readonly || aMode == FileMode::Readwrite);
+
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mStorage);
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ auto* fileHandle = static_cast<FileHandle*>(aActor);
+
+ // Add a placeholder for this file handle immediately.
+ fileHandleThreadPool->Enqueue(fileHandle, nullptr, false);
+
+ fileHandle->SetActive();
+
+ if (NS_WARN_IF(!RegisterFileHandle(fileHandle))) {
+ fileHandle->Abort(/* aForce */ false);
+ return IPC_OK();
+ }
+
+ return IPC_OK();
+}
+
+bool BackgroundMutableFileParentBase::DeallocPBackgroundFileHandleParent(
+ PBackgroundFileHandleParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ RefPtr<FileHandle> fileHandle = dont_AddRef(static_cast<FileHandle*>(aActor));
+ return true;
+}
+
+mozilla::ipc::IPCResult BackgroundMutableFileParentBase::RecvDeleteMe() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mActorDestroyed);
+
+ IProtocol* mgr = Manager();
+ if (!PBackgroundMutableFileParent::Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult BackgroundMutableFileParentBase::RecvGetFileId(
+ int64_t* aFileId) {
+ AssertIsOnBackgroundThread();
+
+ *aFileId = -1;
+ return IPC_OK();
+}
+
+/*******************************************************************************
+ * FileHandle
+ ******************************************************************************/
+
+FileHandle::FileHandle(BackgroundMutableFileParentBase* aMutableFile,
+ FileMode aMode)
+ : mMutableFile(aMutableFile),
+ mActiveRequestCount(0),
+ mStorage(aMutableFile->Storage()),
+ mInvalidatedOnAnyThread(false),
+ mMode(aMode),
+ mHasBeenActive(false),
+ mActorDestroyed(false),
+ mInvalidated(false),
+ mAborted(false),
+ mFinishOrAbortReceived(false),
+ mFinishedOrAborted(false),
+ mForceAborted(false) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aMutableFile);
+
+#ifdef DEBUG
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mStorage);
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ mThreadPoolEventTarget = fileHandleThreadPool->GetThreadPoolEventTarget();
+#endif
+}
+
+FileHandle::~FileHandle() {
+ MOZ_ASSERT(!mActiveRequestCount);
+ MOZ_ASSERT(mActorDestroyed);
+ MOZ_ASSERT_IF(mHasBeenActive, mFinishedOrAborted);
+}
+
+void FileHandle::AssertIsOnThreadPool() const {
+ MOZ_ASSERT(mThreadPoolEventTarget);
+ DebugOnly<bool> current;
+ MOZ_ASSERT(NS_SUCCEEDED(mThreadPoolEventTarget->IsOnCurrentThread(&current)));
+ MOZ_ASSERT(current);
+}
+
+nsresult FileHandle::GetOrCreateStream(nsISupports** aStream) {
+ AssertIsOnBackgroundThread();
+
+ if (!mStream) {
+ nsCOMPtr<nsISupports> stream =
+ mMutableFile->CreateStream(mMode == FileMode::Readonly);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ stream.swap(mStream);
+ }
+
+ nsCOMPtr<nsISupports> stream(mStream);
+ stream.forget(aStream);
+
+ return NS_OK;
+}
+
+void FileHandle::Abort(bool aForce) {
+ AssertIsOnBackgroundThread();
+
+ mAborted = true;
+
+ if (aForce) {
+ mForceAborted = true;
+ }
+
+ MaybeFinishOrAbort();
+}
+
+void FileHandle::NoteActiveRequest() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
+
+ mActiveRequestCount++;
+}
+
+void FileHandle::NoteFinishedRequest() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount);
+
+ mActiveRequestCount--;
+
+ MaybeFinishOrAbort();
+}
+
+void FileHandle::Invalidate() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
+
+ if (!mInvalidated) {
+ mInvalidated = true;
+ mInvalidatedOnAnyThread = true;
+
+ Abort(/* aForce */ true);
+ }
+}
+
+void FileHandle::SendCompleteNotification(bool aAborted) {
+ AssertIsOnBackgroundThread();
+
+ if (!IsActorDestroyed()) {
+ Unused << SendComplete(aAborted);
+ }
+}
+
+bool FileHandle::VerifyRequestParams(const FileRequestParams& aParams) const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+ switch (aParams.type()) {
+ case FileRequestParams::TFileRequestGetMetadataParams: {
+ const FileRequestGetMetadataParams& params =
+ aParams.get_FileRequestGetMetadataParams();
+
+ if (NS_WARN_IF(!params.size() && !params.lastModified())) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestReadParams: {
+ const FileRequestReadParams& params = aParams.get_FileRequestReadParams();
+
+ if (NS_WARN_IF(params.offset() == UINT64_MAX)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!params.size())) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(params.size() > UINT32_MAX)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestWriteParams: {
+ if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ const FileRequestWriteParams& params =
+ aParams.get_FileRequestWriteParams();
+
+ if (NS_WARN_IF(!params.dataLength())) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ if (NS_WARN_IF(!VerifyRequestData(params.data()))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestTruncateParams: {
+ if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ const FileRequestTruncateParams& params =
+ aParams.get_FileRequestTruncateParams();
+
+ if (NS_WARN_IF(params.offset() == UINT64_MAX)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestParams::TFileRequestFlushParams: {
+ if (NS_WARN_IF(mMode != FileMode::Readwrite)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+bool FileHandle::VerifyRequestData(const FileRequestData& aData) const {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aData.type() != FileRequestData::T__None);
+
+ switch (aData.type()) {
+ case FileRequestData::TFileRequestStringData: {
+ const FileRequestStringData& data = aData.get_FileRequestStringData();
+
+ if (NS_WARN_IF(data.string().IsEmpty())) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return false;
+ }
+
+ break;
+ }
+
+ case FileRequestData::TFileRequestBlobData: {
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ return true;
+}
+
+void FileHandle::FinishOrAbort() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!mFinishedOrAborted);
+
+ mFinishedOrAborted = true;
+
+ if (!mHasBeenActive) {
+ return;
+ }
+
+ RefPtr<FinishOp> finishOp = new FinishOp(this, mAborted);
+
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mStorage);
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ fileHandleThreadPool->Enqueue(this, finishOp, true);
+}
+
+void FileHandle::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnBackgroundThread();
+
+ MOZ_ASSERT(!mActorDestroyed);
+
+ mActorDestroyed = true;
+
+ if (!mFinishedOrAborted) {
+ mAborted = true;
+
+ mForceAborted = true;
+
+ MaybeFinishOrAbort();
+ }
+}
+
+mozilla::ipc::IPCResult FileHandle::RecvDeleteMe() {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(!IsActorDestroyed());
+
+ IProtocol* mgr = Manager();
+ if (!PBackgroundFileHandleParent::Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FileHandle::RecvFinish() {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mFinishOrAbortReceived)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mFinishOrAbortReceived = true;
+
+ MaybeFinishOrAbort();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FileHandle::RecvAbort() {
+ AssertIsOnBackgroundThread();
+
+ if (NS_WARN_IF(mFinishOrAbortReceived)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ mFinishOrAbortReceived = true;
+
+ Abort(/* aForce */ false);
+ return IPC_OK();
+}
+
+PBackgroundFileRequestParent* FileHandle::AllocPBackgroundFileRequestParent(
+ const FileRequestParams& aParams) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+#ifdef DEBUG
+ // Always verify parameters in DEBUG builds!
+ bool trustParams = false;
+#else
+ PBackgroundParent* backgroundActor = GetBackgroundParent();
+ MOZ_ASSERT(backgroundActor);
+
+ bool trustParams = !BackgroundParent::IsOtherProcessActor(backgroundActor);
+#endif
+
+ if (NS_WARN_IF(!trustParams && !VerifyRequestParams(aParams))) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(mFinishOrAbortReceived)) {
+ MOZ_CRASH_UNLESS_FUZZING();
+ return nullptr;
+ }
+
+ RefPtr<NormalFileHandleOp> actor;
+
+ switch (aParams.type()) {
+ case FileRequestParams::TFileRequestGetMetadataParams:
+ actor = new GetMetadataOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestReadParams:
+ actor = new ReadOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestWriteParams:
+ actor = new WriteOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestTruncateParams:
+ actor = new TruncateOp(this, aParams);
+ break;
+
+ case FileRequestParams::TFileRequestFlushParams:
+ actor = new FlushOp(this, aParams);
+ break;
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ MOZ_ASSERT(actor);
+
+ // Transfer ownership to IPDL.
+ return actor.forget().take();
+}
+
+mozilla::ipc::IPCResult FileHandle::RecvPBackgroundFileRequestConstructor(
+ PBackgroundFileRequestParent* aActor, const FileRequestParams& aParams) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+ auto* op = static_cast<NormalFileHandleOp*>(aActor);
+
+ if (NS_WARN_IF(!op->Init(this))) {
+ op->Cleanup();
+ return IPC_FAIL_NO_REASON(this);
+ }
+
+ op->Enqueue();
+ return IPC_OK();
+}
+
+bool FileHandle::DeallocPBackgroundFileRequestParent(
+ PBackgroundFileRequestParent* aActor) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aActor);
+
+ // Transfer ownership back from IPDL.
+ RefPtr<NormalFileHandleOp> actor =
+ dont_AddRef(static_cast<NormalFileHandleOp*>(aActor));
+ return true;
+}
+
+/*******************************************************************************
+ * Local class implementations
+ ******************************************************************************/
+
+void FileHandleOp::Enqueue() {
+ AssertIsOnOwningThread();
+
+ FileHandleThreadPool* fileHandleThreadPool =
+ GetFileHandleThreadPoolFor(mFileHandle->Storage());
+ MOZ_ASSERT(fileHandleThreadPool);
+
+ fileHandleThreadPool->Enqueue(mFileHandle, this, false);
+
+#ifdef DEBUG
+ mEnqueued = true;
+#endif
+
+ mFileHandle->NoteActiveRequest();
+}
+
+void FileHandle::FinishOp::RunOnThreadPool() {
+ AssertIsOnThreadPool();
+ MOZ_ASSERT(mFileHandle);
+
+ nsCOMPtr<nsISupports>& stream = mFileHandle->mStream;
+
+ if (!stream) {
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(stream);
+ MOZ_ASSERT(inputStream);
+
+ MOZ_ALWAYS_SUCCEEDS(inputStream->Close());
+
+ stream = nullptr;
+}
+
+void FileHandle::FinishOp::RunOnOwningThread() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFileHandle);
+
+ mFileHandle->SendCompleteNotification(mAborted);
+
+ mFileHandle->GetMutableFile()->UnregisterFileHandle(mFileHandle);
+
+ mFileHandle = nullptr;
+}
+
+NormalFileHandleOp::~NormalFileHandleOp() {
+ MOZ_ASSERT(!mFileHandle,
+ "NormalFileHandleOp::Cleanup() was not called by a subclass!");
+}
+
+bool NormalFileHandleOp::Init(FileHandle* aFileHandle) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ nsresult rv = aFileHandle->GetOrCreateStream(getter_AddRefs(mFileStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return true;
+}
+
+void NormalFileHandleOp::Cleanup() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFileHandle);
+ MOZ_ASSERT_IF(mEnqueued && !IsActorDestroyed(), mResponseSent);
+
+ mFileHandle = nullptr;
+}
+
+nsresult NormalFileHandleOp::SendSuccessResult() {
+ AssertIsOnOwningThread();
+
+ if (!IsActorDestroyed()) {
+ FileRequestResponse response;
+ GetResponse(response);
+
+ MOZ_ASSERT(response.type() != FileRequestResponse::T__None);
+
+ if (response.type() == FileRequestResponse::Tnsresult) {
+ MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
+
+ return response.get_nsresult();
+ }
+
+ if (NS_WARN_IF(
+ !PBackgroundFileRequestParent::Send__delete__(this, response))) {
+ return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ }
+ }
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+
+ return NS_OK;
+}
+
+bool NormalFileHandleOp::SendFailureResult(nsresult aResultCode) {
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(NS_FAILED(aResultCode));
+
+ bool result = false;
+
+ if (!IsActorDestroyed()) {
+ result = PBackgroundFileRequestParent::Send__delete__(
+ this, ClampResultCode(aResultCode));
+ }
+
+#ifdef DEBUG
+ mResponseSent = true;
+#endif
+
+ return result;
+}
+
+void NormalFileHandleOp::RunOnThreadPool() {
+ AssertIsOnThreadPool();
+ MOZ_ASSERT(mFileHandle);
+ MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
+
+ // There are several cases where we don't actually have to to any work here.
+
+ if (mFileHandleIsAborted) {
+ // This transaction is already set to be aborted.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
+ } else if (mFileHandle->IsInvalidatedOnAnyThread()) {
+ // This file handle is being invalidated.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ } else if (!OperationMayProceed()) {
+ // The operation was canceled in some way, likely because the child process
+ // has crashed.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ } else {
+ nsresult rv = DoFileWork(mFileHandle);
+ if (NS_FAILED(rv)) {
+ mResultCode = rv;
+ }
+ }
+}
+
+void NormalFileHandleOp::RunOnOwningThread() {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mFileHandle);
+
+ if (NS_WARN_IF(IsActorDestroyed())) {
+ // Don't send any notifications if the actor was destroyed already.
+ if (NS_SUCCEEDED(mResultCode)) {
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ }
+ } else {
+ if (mFileHandle->IsInvalidated()) {
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
+ } else if (mFileHandle->IsAborted()) {
+ // Aborted file handles always see their requests fail with ABORT_ERR,
+ // even if the request succeeded or failed with another error.
+ mResultCode = NS_ERROR_DOM_FILEHANDLE_ABORT_ERR;
+ } else if (NS_SUCCEEDED(mResultCode)) {
+ // This may release the IPDL reference.
+ mResultCode = SendSuccessResult();
+ }
+
+ if (NS_FAILED(mResultCode)) {
+ // This should definitely release the IPDL reference.
+ if (!SendFailureResult(mResultCode)) {
+ // Abort the file handle.
+ mFileHandle->Abort(/* aForce */ false);
+ }
+ }
+ }
+
+ mFileHandle->NoteFinishedRequest();
+
+ Cleanup();
+}
+
+void NormalFileHandleOp::ActorDestroy(ActorDestroyReason aWhy) {
+ AssertIsOnOwningThread();
+
+ NoteActorDestroyed();
+}
+
+nsresult CopyFileHandleOp::DoFileWork(FileHandle* aFileHandle) {
+ AssertIsOnThreadPool();
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIOutputStream> outputStream;
+
+ if (mRead) {
+ inputStream = do_QueryInterface(mFileStream);
+ outputStream = do_QueryInterface(mBufferStream);
+ } else {
+ inputStream = do_QueryInterface(mBufferStream);
+ outputStream = do_QueryInterface(mFileStream);
+ }
+
+ MOZ_ASSERT(inputStream);
+ MOZ_ASSERT(outputStream);
+
+ nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(mFileStream);
+
+ nsresult rv;
+
+ if (seekableStream) {
+ if (mOffset == UINT64_MAX) {
+ rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0);
+ } else {
+ rv = seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, mOffset);
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ mOffset = 0;
+
+ do {
+ char copyBuffer[kStreamCopyBlockSize];
+
+ uint64_t max = mSize - mOffset;
+ if (max == 0) {
+ break;
+ }
+
+ uint32_t count = sizeof(copyBuffer);
+ if (count > max) {
+ count = max;
+ }
+
+ uint32_t numRead;
+ rv = inputStream->Read(copyBuffer, count, &numRead);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!numRead) {
+ break;
+ }
+
+ uint32_t numWrite;
+ rv = outputStream->Write(copyBuffer, numRead, &numWrite);
+ if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
+ rv = NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR;
+ }
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(numWrite != numRead)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mOffset += numWrite;
+
+ nsCOMPtr<nsIRunnable> runnable = new ProgressRunnable(this, mOffset, mSize);
+
+ mOwningEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ } while (true);
+
+ if (mOffset < mSize) {
+ // end-of-file reached
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(mOffset == mSize);
+
+ if (mRead) {
+ MOZ_ALWAYS_SUCCEEDS(outputStream->Close());
+ } else {
+ MOZ_ALWAYS_SUCCEEDS(inputStream->Close());
+ }
+
+ return NS_OK;
+}
+
+void CopyFileHandleOp::Cleanup() {
+ AssertIsOnOwningThread();
+
+ mBufferStream = nullptr;
+
+ NormalFileHandleOp::Cleanup();
+}
+
+NS_IMETHODIMP
+CopyFileHandleOp::ProgressRunnable::Run() {
+ AssertIsOnBackgroundThread();
+
+ Unused << mCopyFileHandleOp->SendProgress(mProgress, mProgressMax);
+
+ mCopyFileHandleOp = nullptr;
+
+ return NS_OK;
+}
+
+GetMetadataOp::GetMetadataOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : NormalFileHandleOp(aFileHandle),
+ mParams(aParams.get_FileRequestGetMetadataParams()) {
+ MOZ_ASSERT(aParams.type() ==
+ FileRequestParams::TFileRequestGetMetadataParams);
+}
+
+nsresult GetMetadataOp::DoFileWork(FileHandle* aFileHandle) {
+ AssertIsOnThreadPool();
+
+ nsresult rv;
+
+ if (mFileHandle->Mode() == FileMode::Readwrite) {
+ // Force a flush (so all pending writes are flushed to the disk and file
+ // metadata is updated too).
+
+ nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(ostream);
+
+ rv = ostream->Flush();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIFileMetadata> metadata = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(metadata);
+
+ if (mParams.size()) {
+ int64_t size;
+ rv = metadata->GetSize(&size);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(size < 0)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mMetadata.size() = Some(uint64_t(size));
+ } else {
+ mMetadata.size() = Nothing();
+ }
+
+ if (mParams.lastModified()) {
+ int64_t lastModified;
+ rv = metadata->GetLastModified(&lastModified);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mMetadata.lastModified() = Some(lastModified);
+ } else {
+ mMetadata.lastModified() = Nothing();
+ }
+
+ return NS_OK;
+}
+
+void GetMetadataOp::GetResponse(FileRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ aResponse = FileRequestGetMetadataResponse(mMetadata);
+}
+
+ReadOp::ReadOp(FileHandle* aFileHandle, const FileRequestParams& aParams)
+ : CopyFileHandleOp(aFileHandle),
+ mParams(aParams.get_FileRequestReadParams()) {
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestReadParams);
+}
+
+bool ReadOp::Init(FileHandle* aFileHandle) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ if (NS_WARN_IF(!NormalFileHandleOp::Init(aFileHandle))) {
+ return false;
+ }
+
+ if (NS_WARN_IF(mParams.size() > std::numeric_limits<std::size_t>::max())) {
+ return false;
+ }
+
+ mBufferStream =
+ FixedBufferOutputStream::Create(mParams.size(), fallible).forget();
+ if (NS_WARN_IF(!mBufferStream)) {
+ return false;
+ }
+
+ mOffset = mParams.offset();
+ mSize = mParams.size();
+ mRead = true;
+
+ return true;
+}
+
+void ReadOp::GetResponse(FileRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+
+ auto* stream = static_cast<FixedBufferOutputStream*>(mBufferStream.get());
+
+ aResponse = FileRequestReadResponse(nsCString(stream->WrittenData()));
+}
+
+WriteOp::WriteOp(FileHandle* aFileHandle, const FileRequestParams& aParams)
+ : CopyFileHandleOp(aFileHandle),
+ mParams(aParams.get_FileRequestWriteParams()) {
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestWriteParams);
+}
+
+bool WriteOp::Init(FileHandle* aFileHandle) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+
+ if (NS_WARN_IF(!NormalFileHandleOp::Init(aFileHandle))) {
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ const FileRequestData& data = mParams.data();
+ switch (data.type()) {
+ case FileRequestData::TFileRequestStringData: {
+ const FileRequestStringData& stringData =
+ data.get_FileRequestStringData();
+
+ const nsCString& string = stringData.string();
+
+ nsresult rv =
+ NS_NewCStringInputStream(getter_AddRefs(inputStream), string);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ break;
+ }
+ case FileRequestData::TFileRequestBlobData: {
+ const FileRequestBlobData& blobData = data.get_FileRequestBlobData();
+
+ RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobData.blob());
+ if (NS_WARN_IF(!blobImpl)) {
+ return false;
+ }
+
+ IgnoredErrorResult rv;
+ blobImpl->CreateInputStream(getter_AddRefs(inputStream), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return false;
+ }
+
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Should never get here!");
+ }
+
+ mBufferStream = inputStream;
+ mOffset = mParams.offset();
+ mSize = mParams.dataLength();
+ mRead = false;
+
+ return true;
+}
+
+void WriteOp::GetResponse(FileRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+ aResponse = FileRequestWriteResponse();
+}
+
+TruncateOp::TruncateOp(FileHandle* aFileHandle,
+ const FileRequestParams& aParams)
+ : NormalFileHandleOp(aFileHandle),
+ mParams(aParams.get_FileRequestTruncateParams()) {
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestTruncateParams);
+}
+
+nsresult TruncateOp::DoFileWork(FileHandle* aFileHandle) {
+ AssertIsOnThreadPool();
+
+ nsCOMPtr<nsISeekableStream> sstream = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(sstream);
+
+ if (mParams.offset()) {
+ nsCOMPtr<nsIFileMetadata> fileMetadata = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(fileMetadata);
+
+ int64_t size;
+ nsresult rv = fileMetadata->GetSize(&size);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ MOZ_ASSERT(size >= 0);
+
+ if (mParams.offset() > static_cast<uint64_t>(size)) {
+ // Cannot extend the size of a file through truncate.
+ return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
+ }
+ }
+
+ // XXX If we allowed truncate to extend the file size, we would to ensure that
+ // the quota limit is checked, e.g. by making FileQuotaStream override Seek.
+ nsresult rv = sstream->Seek(nsISeekableStream::NS_SEEK_SET, mParams.offset());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = sstream->SetEOF();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void TruncateOp::GetResponse(FileRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+ aResponse = FileRequestTruncateResponse();
+}
+
+FlushOp::FlushOp(FileHandle* aFileHandle, const FileRequestParams& aParams)
+ : NormalFileHandleOp(aFileHandle),
+ mParams(aParams.get_FileRequestFlushParams()) {
+ MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestFlushParams);
+}
+
+nsresult FlushOp::DoFileWork(FileHandle* aFileHandle) {
+ AssertIsOnThreadPool();
+
+ nsCOMPtr<nsIOutputStream> ostream = do_QueryInterface(mFileStream);
+ MOZ_ASSERT(ostream);
+
+ nsresult rv = ostream->Flush();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void FlushOp::GetResponse(FileRequestResponse& aResponse) {
+ AssertIsOnOwningThread();
+ aResponse = FileRequestFlushResponse();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/filehandle/ActorsParent.h b/dom/filehandle/ActorsParent.h
new file mode 100644
index 0000000000..319c50a0cf
--- /dev/null
+++ b/dom/filehandle/ActorsParent.h
@@ -0,0 +1,174 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_filehandle_ActorsParent_h
+#define mozilla_dom_filehandle_ActorsParent_h
+
+#include "mozilla/dom/FileHandleStorage.h"
+#include "mozilla/dom/PBackgroundMutableFileParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/UniquePtr.h"
+#include "nsClassHashtable.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "nsTArrayForwardDeclare.h"
+#include "nsTHashSet.h"
+
+template <class>
+struct already_AddRefed;
+class nsIFile;
+class nsIRunnable;
+class nsIThreadPool;
+
+namespace mozilla {
+
+namespace ipc {
+
+class PBackgroundParent;
+
+} // namespace ipc
+
+namespace dom {
+
+class BlobImpl;
+class FileHandle;
+class FileHandleOp;
+
+class FileHandleThreadPool final {
+ class FileHandleQueue;
+ struct DelayedEnqueueInfo;
+ class DirectoryInfo;
+ struct StoragesCompleteCallback;
+
+ nsCOMPtr<nsIThreadPool> mThreadPool;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ nsClassHashtable<nsCStringHashKey, DirectoryInfo> mDirectoryInfos;
+
+ nsTArray<UniquePtr<StoragesCompleteCallback>> mCompleteCallbacks;
+
+ bool mShutdownRequested;
+ bool mShutdownComplete;
+
+ public:
+ static already_AddRefed<FileHandleThreadPool> Create();
+
+#ifdef DEBUG
+ void AssertIsOnOwningThread() const;
+
+ nsIEventTarget* GetThreadPoolEventTarget() const;
+#else
+ void AssertIsOnOwningThread() const {}
+#endif
+
+ void Enqueue(FileHandle* aFileHandle, FileHandleOp* aFileHandleOp,
+ bool aFinish);
+
+ NS_INLINE_DECL_REFCOUNTING(FileHandleThreadPool)
+
+ void WaitForDirectoriesToComplete(nsTArray<nsCString>&& aDirectoryIds,
+ nsIRunnable* aCallback);
+
+ void Shutdown();
+
+ private:
+ FileHandleThreadPool();
+
+ // Reference counted.
+ ~FileHandleThreadPool();
+
+ nsresult Init();
+
+ void Cleanup();
+
+ void FinishFileHandle(FileHandle* aFileHandle);
+
+ bool MaybeFireCallback(StoragesCompleteCallback* aCallback);
+};
+
+class BackgroundMutableFileParentBase : public PBackgroundMutableFileParent {
+ friend PBackgroundMutableFileParent;
+
+ nsTHashSet<FileHandle*> mFileHandles;
+ nsCString mDirectoryId;
+ nsString mFileName;
+ FileHandleStorage mStorage;
+ bool mInvalidated;
+ bool mActorWasAlive;
+ bool mActorDestroyed;
+
+ protected:
+ nsCOMPtr<nsIFile> mFile;
+
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BackgroundMutableFileParentBase)
+
+ void Invalidate();
+
+ FileHandleStorage Storage() const { return mStorage; }
+
+ const nsCString& DirectoryId() const { return mDirectoryId; }
+
+ const nsString& FileName() const { return mFileName; }
+
+ bool RegisterFileHandle(FileHandle* aFileHandle);
+
+ void UnregisterFileHandle(FileHandle* aFileHandle);
+
+ void SetActorAlive();
+
+ bool IsActorDestroyed() const {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ return mActorWasAlive && mActorDestroyed;
+ }
+
+ bool IsInvalidated() const {
+ mozilla::ipc::AssertIsOnBackgroundThread();
+
+ return mInvalidated;
+ }
+
+ virtual void NoteActiveState() {}
+
+ virtual void NoteInactiveState() {}
+
+ virtual mozilla::ipc::PBackgroundParent* GetBackgroundParent() const = 0;
+
+ virtual already_AddRefed<nsISupports> CreateStream(bool aReadOnly);
+
+ virtual already_AddRefed<BlobImpl> CreateBlobImpl() { return nullptr; }
+
+ protected:
+ BackgroundMutableFileParentBase(FileHandleStorage aStorage,
+ const nsACString& aDirectoryId,
+ const nsAString& aFileName, nsIFile* aFile);
+
+ // Reference counted.
+ ~BackgroundMutableFileParentBase();
+
+ // IPDL methods are only called by IPDL.
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual PBackgroundFileHandleParent* AllocPBackgroundFileHandleParent(
+ const FileMode& aMode);
+
+ virtual mozilla::ipc::IPCResult RecvPBackgroundFileHandleConstructor(
+ PBackgroundFileHandleParent* aActor, const FileMode& aMode) override;
+
+ virtual bool DeallocPBackgroundFileHandleParent(
+ PBackgroundFileHandleParent* aActor);
+
+ mozilla::ipc::IPCResult RecvDeleteMe();
+
+ virtual mozilla::ipc::IPCResult RecvGetFileId(int64_t* aFileId);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_filehandle_ActorsParent_h
diff --git a/dom/filehandle/FileHandleStorage.h b/dom/filehandle/FileHandleStorage.h
new file mode 100644
index 0000000000..6be19d853b
--- /dev/null
+++ b/dom/filehandle/FileHandleStorage.h
@@ -0,0 +1,21 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_FileHandleStorage_h
+#define mozilla_dom_FileHandleStorage_h
+
+namespace mozilla::dom {
+
+enum FileHandleStorage {
+ FILE_HANDLE_STORAGE_IDB = 0,
+ // A placeholder for bug 997471
+ // FILE_HANDLE_STORAGE_FS
+ FILE_HANDLE_STORAGE_MAX
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_FileHandleStorage_h
diff --git a/dom/filehandle/PBackgroundFileHandle.ipdl b/dom/filehandle/PBackgroundFileHandle.ipdl
new file mode 100644
index 0000000000..f2881ad5d4
--- /dev/null
+++ b/dom/filehandle/PBackgroundFileHandle.ipdl
@@ -0,0 +1,90 @@
+/* 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 protocol PBackgroundFileRequest;
+include protocol PBackgroundMutableFile;
+
+include IPCBlob;
+
+include "mozilla/dom/indexedDB/ActorsChild.h";
+
+namespace mozilla {
+namespace dom {
+
+struct FileRequestGetMetadataParams
+{
+ bool size;
+ bool lastModified;
+};
+
+struct FileRequestReadParams
+{
+ uint64_t offset;
+ uint64_t size;
+};
+
+struct FileRequestStringData
+{
+ nsCString string;
+};
+
+struct FileRequestBlobData
+{
+ IPCBlob blob;
+};
+
+union FileRequestData
+{
+ FileRequestStringData;
+ FileRequestBlobData;
+};
+
+struct FileRequestWriteParams
+{
+ uint64_t offset;
+ FileRequestData data;
+ uint64_t dataLength;
+};
+
+struct FileRequestTruncateParams
+{
+ uint64_t offset;
+};
+
+struct FileRequestFlushParams
+{
+};
+
+union FileRequestParams
+{
+ FileRequestGetMetadataParams;
+ FileRequestReadParams;
+ FileRequestWriteParams;
+ FileRequestTruncateParams;
+ FileRequestFlushParams;
+};
+
+[ManualDealloc, ChildImpl="indexedDB::BackgroundFileHandleChild", ParentImpl=virtual]
+protocol PBackgroundFileHandle
+{
+ manager PBackgroundMutableFile;
+
+ manages PBackgroundFileRequest;
+
+parent:
+ async DeleteMe();
+
+ async Finish();
+ async Abort();
+
+ async PBackgroundFileRequest(FileRequestParams params);
+
+child:
+ async __delete__();
+
+ async Complete(bool aborted);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filehandle/PBackgroundFileRequest.ipdl b/dom/filehandle/PBackgroundFileRequest.ipdl
new file mode 100644
index 0000000000..a013112b0c
--- /dev/null
+++ b/dom/filehandle/PBackgroundFileRequest.ipdl
@@ -0,0 +1,65 @@
+/* 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 protocol PBackgroundFileHandle;
+
+include "mozilla/dom/indexedDB/ActorsChild.h";
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+
+namespace mozilla {
+namespace dom {
+
+struct FileRequestMetadata
+{
+ uint64_t? size;
+ int64_t? lastModified;
+};
+
+struct FileRequestGetMetadataResponse
+{
+ FileRequestMetadata metadata;
+};
+
+struct FileRequestReadResponse
+{
+ nsCString data;
+};
+
+struct FileRequestWriteResponse
+{
+};
+
+struct FileRequestTruncateResponse
+{
+};
+
+struct FileRequestFlushResponse
+{
+};
+
+union FileRequestResponse
+{
+ nsresult;
+ FileRequestGetMetadataResponse;
+ FileRequestReadResponse;
+ FileRequestWriteResponse;
+ FileRequestTruncateResponse;
+ FileRequestFlushResponse;
+};
+
+[ManualDealloc, ChildImpl="indexedDB::BackgroundFileRequestChild", ParentImpl=virtual]
+protocol PBackgroundFileRequest
+{
+ manager PBackgroundFileHandle;
+
+child:
+ async __delete__(FileRequestResponse response);
+
+ async Progress(uint64_t progress,
+ uint64_t progressMax);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filehandle/PBackgroundMutableFile.ipdl b/dom/filehandle/PBackgroundMutableFile.ipdl
new file mode 100644
index 0000000000..334aa32564
--- /dev/null
+++ b/dom/filehandle/PBackgroundMutableFile.ipdl
@@ -0,0 +1,39 @@
+/* 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 protocol PBackgroundFileHandle;
+include protocol PBackgroundIDBDatabase;
+
+include "mozilla/dom/filehandle/SerializationHelpers.h";
+include "mozilla/dom/indexedDB/ActorsChild.h";
+include "mozilla/dom/filehandle/ActorsParent.h";
+
+using mozilla::dom::FileMode
+ from "mozilla/dom/FileModeBinding.h";
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl="indexedDB::BackgroundMutableFileChild", ParentImpl="BackgroundMutableFileParentBase"]
+sync protocol PBackgroundMutableFile
+{
+ manager PBackgroundIDBDatabase;
+
+ manages PBackgroundFileHandle;
+
+parent:
+ async DeleteMe();
+
+ async PBackgroundFileHandle(FileMode mode);
+
+ // Use only for testing!
+ sync GetFileId()
+ returns (int64_t fileId);
+
+child:
+ async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/filehandle/SerializationHelpers.h b/dom/filehandle/SerializationHelpers.h
new file mode 100644
index 0000000000..3bf66e1f69
--- /dev/null
+++ b/dom/filehandle/SerializationHelpers.h
@@ -0,0 +1,24 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_filehandle_SerializationHelpers_h
+#define mozilla_dom_filehandle_SerializationHelpers_h
+
+#include "ipc/EnumSerializer.h"
+
+#include "mozilla/dom/FileModeBinding.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::FileMode>
+ : public ContiguousEnumSerializer<mozilla::dom::FileMode,
+ mozilla::dom::FileMode::Readonly,
+ mozilla::dom::FileMode::EndGuard_> {};
+
+} // namespace IPC
+
+#endif // mozilla_dom_filehandle_SerializationHelpers_h
diff --git a/dom/filehandle/moz.build b/dom/filehandle/moz.build
new file mode 100644
index 0000000000..929163b3d3
--- /dev/null
+++ b/dom/filehandle/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: File")
+
+EXPORTS.mozilla.dom.filehandle += [
+ "ActorsParent.h",
+ "SerializationHelpers.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "FileHandleStorage.h",
+]
+
+UNIFIED_SOURCES += [
+ "ActorsParent.cpp",
+]
+
+IPDL_SOURCES += [
+ "PBackgroundFileHandle.ipdl",
+ "PBackgroundFileRequest.ipdl",
+ "PBackgroundMutableFile.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "../base",
+]
+
+FINAL_LIBRARY = "xul"