diff options
Diffstat (limited to '')
-rw-r--r-- | dom/filehandle/ActorsParent.cpp | 2190 | ||||
-rw-r--r-- | dom/filehandle/ActorsParent.h | 174 | ||||
-rw-r--r-- | dom/filehandle/FileHandleStorage.h | 21 | ||||
-rw-r--r-- | dom/filehandle/PBackgroundFileHandle.ipdl | 90 | ||||
-rw-r--r-- | dom/filehandle/PBackgroundFileRequest.ipdl | 65 | ||||
-rw-r--r-- | dom/filehandle/PBackgroundMutableFile.ipdl | 39 | ||||
-rw-r--r-- | dom/filehandle/SerializationHelpers.h | 24 | ||||
-rw-r--r-- | dom/filehandle/moz.build | 35 |
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(¤t))); + 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(¤t)); + 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(¤t))); + 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" |