diff options
Diffstat (limited to 'dom/filehandle')
-rw-r--r-- | dom/filehandle/ActorsChild.cpp | 743 | ||||
-rw-r--r-- | dom/filehandle/ActorsChild.h | 174 | ||||
-rw-r--r-- | dom/filehandle/ActorsParent.cpp | 2666 | ||||
-rw-r--r-- | dom/filehandle/ActorsParent.h | 221 | ||||
-rw-r--r-- | dom/filehandle/FileHandleBase.cpp | 636 | ||||
-rw-r--r-- | dom/filehandle/FileHandleBase.h | 246 | ||||
-rw-r--r-- | dom/filehandle/FileHandleCommon.cpp | 34 | ||||
-rw-r--r-- | dom/filehandle/FileHandleCommon.h | 73 | ||||
-rw-r--r-- | dom/filehandle/FileHandleStorage.h | 24 | ||||
-rw-r--r-- | dom/filehandle/FileRequestBase.h | 93 | ||||
-rw-r--r-- | dom/filehandle/MutableFileBase.cpp | 36 | ||||
-rw-r--r-- | dom/filehandle/MutableFileBase.h | 76 | ||||
-rw-r--r-- | dom/filehandle/PBackgroundFileHandle.ipdl | 91 | ||||
-rw-r--r-- | dom/filehandle/PBackgroundFileRequest.ipdl | 83 | ||||
-rw-r--r-- | dom/filehandle/PBackgroundMutableFile.ipdl | 36 | ||||
-rw-r--r-- | dom/filehandle/SerializationHelpers.h | 23 | ||||
-rw-r--r-- | dom/filehandle/moz.build | 45 |
17 files changed, 5300 insertions, 0 deletions
diff --git a/dom/filehandle/ActorsChild.cpp b/dom/filehandle/ActorsChild.cpp new file mode 100644 index 000000000..7a0b622f8 --- /dev/null +++ b/dom/filehandle/ActorsChild.cpp @@ -0,0 +1,743 @@ +/* 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 "ActorsChild.h" + +#include "BackgroundChildImpl.h" +#include "FileHandleBase.h" +#include "FileRequestBase.h" +#include "js/Date.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "MutableFileBase.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "xpcpublic.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +/******************************************************************************* + * Helpers + ******************************************************************************/ + +namespace { + +class MOZ_STACK_CLASS AutoSetCurrentFileHandle final +{ + typedef mozilla::ipc::BackgroundChildImpl BackgroundChildImpl; + + FileHandleBase* const mFileHandle; + FileHandleBase* mPreviousFileHandle; + FileHandleBase** mThreadLocalSlot; + +public: + explicit AutoSetCurrentFileHandle(FileHandleBase* aFileHandle) + : mFileHandle(aFileHandle) + , mPreviousFileHandle(nullptr) + , mThreadLocalSlot(nullptr) + { + if (aFileHandle) { + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + MOZ_ASSERT(threadLocal); + + // Hang onto this location for resetting later. + mThreadLocalSlot = &threadLocal->mCurrentFileHandle; + + // Save the current value. + mPreviousFileHandle = *mThreadLocalSlot; + + // Set the new value. + *mThreadLocalSlot = aFileHandle; + } + } + + ~AutoSetCurrentFileHandle() + { + MOZ_ASSERT_IF(mThreadLocalSlot, mFileHandle); + MOZ_ASSERT_IF(mThreadLocalSlot, *mThreadLocalSlot == mFileHandle); + + if (mThreadLocalSlot) { + // Reset old value. + *mThreadLocalSlot = mPreviousFileHandle; + } + } + + FileHandleBase* + FileHandle() const + { + return mFileHandle; + } +}; + +class MOZ_STACK_CLASS ResultHelper final + : public FileRequestBase::ResultCallback +{ + FileRequestBase* mFileRequest; + AutoSetCurrentFileHandle mAutoFileHandle; + + union + { + File* mFile; + const nsCString* mString; + const FileRequestMetadata* mMetadata; + const JS::Handle<JS::Value>* mJSValHandle; + } mResult; + + enum + { + ResultTypeFile, + ResultTypeString, + ResultTypeMetadata, + ResultTypeJSValHandle, + } mResultType; + +public: + ResultHelper(FileRequestBase* aFileRequest, + FileHandleBase* aFileHandle, + File* aResult) + : mFileRequest(aFileRequest) + , mAutoFileHandle(aFileHandle) + , mResultType(ResultTypeFile) + { + MOZ_ASSERT(aFileRequest); + MOZ_ASSERT(aFileHandle); + MOZ_ASSERT(aResult); + + mResult.mFile = aResult; + } + + ResultHelper(FileRequestBase* aFileRequest, + FileHandleBase* aFileHandle, + const nsCString* aResult) + : mFileRequest(aFileRequest) + , mAutoFileHandle(aFileHandle) + , mResultType(ResultTypeString) + { + MOZ_ASSERT(aFileRequest); + MOZ_ASSERT(aFileHandle); + MOZ_ASSERT(aResult); + + mResult.mString = aResult; + } + + ResultHelper(FileRequestBase* aFileRequest, + FileHandleBase* aFileHandle, + const FileRequestMetadata* aResult) + : mFileRequest(aFileRequest) + , mAutoFileHandle(aFileHandle) + , mResultType(ResultTypeMetadata) + { + MOZ_ASSERT(aFileRequest); + MOZ_ASSERT(aFileHandle); + MOZ_ASSERT(aResult); + + mResult.mMetadata = aResult; + } + + + ResultHelper(FileRequestBase* aFileRequest, + FileHandleBase* aFileHandle, + const JS::Handle<JS::Value>* aResult) + : mFileRequest(aFileRequest) + , mAutoFileHandle(aFileHandle) + , mResultType(ResultTypeJSValHandle) + { + MOZ_ASSERT(aFileRequest); + MOZ_ASSERT(aFileHandle); + MOZ_ASSERT(aResult); + + mResult.mJSValHandle = aResult; + } + + FileRequestBase* + FileRequest() const + { + return mFileRequest; + } + + FileHandleBase* + FileHandle() const + { + return mAutoFileHandle.FileHandle(); + } + + virtual nsresult + GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) override + { + MOZ_ASSERT(aCx); + MOZ_ASSERT(mFileRequest); + + switch (mResultType) { + case ResultTypeFile: + return GetResult(aCx, mResult.mFile, aResult); + + case ResultTypeString: + return GetResult(aCx, mResult.mString, aResult); + + case ResultTypeMetadata: + return GetResult(aCx, mResult.mMetadata, aResult); + + case ResultTypeJSValHandle: + aResult.set(*mResult.mJSValHandle); + return NS_OK; + + default: + MOZ_CRASH("Unknown result type!"); + } + + MOZ_CRASH("Should never get here!"); + } + +private: + nsresult + GetResult(JSContext* aCx, + File* aFile, + JS::MutableHandle<JS::Value> aResult) + { + bool ok = GetOrCreateDOMReflector(aCx, aFile, aResult); + if (NS_WARN_IF(!ok)) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + return NS_OK; + } + + nsresult + GetResult(JSContext* aCx, + const nsCString* aString, + JS::MutableHandle<JS::Value> aResult) + { + const nsCString& data = *aString; + + nsresult rv; + + if (!mFileRequest->HasEncoding()) { + JS::Rooted<JSObject*> arrayBuffer(aCx); + rv = nsContentUtils::CreateArrayBuffer(aCx, data, arrayBuffer.address()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + aResult.setObject(*arrayBuffer); + return NS_OK; + } + + nsAutoCString encoding; + // The BOM sniffing is baked into the "decode" part of the Encoding + // Standard, which the File API references. + if (!nsContentUtils::CheckForBOM( + reinterpret_cast<const unsigned char *>(data.get()), + data.Length(), + encoding)) { + // BOM sniffing failed. Try the API argument. + if (!EncodingUtils::FindEncodingForLabel(mFileRequest->GetEncoding(), + encoding)) { + // API argument failed. Since we are dealing with a file system file, + // we don't have a meaningful type attribute for the blob available, + // so proceeding to the next step, which is defaulting to UTF-8. + encoding.AssignLiteral("UTF-8"); + } + } + + nsString tmpString; + rv = nsContentUtils::ConvertStringFromEncoding(encoding, data, tmpString); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + if (NS_WARN_IF(!xpc::StringToJsval(aCx, tmpString, aResult))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + return NS_OK; + } + + nsresult + GetResult(JSContext* aCx, + const FileRequestMetadata* aMetadata, + JS::MutableHandle<JS::Value> aResult) + { + JS::Rooted<JSObject*> obj(aCx, JS_NewPlainObject(aCx)); + if (NS_WARN_IF(!obj)) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + const FileRequestSize& size = aMetadata->size(); + if (size.type() != FileRequestSize::Tvoid_t) { + MOZ_ASSERT(size.type() == FileRequestSize::Tuint64_t); + + JS::Rooted<JS::Value> number(aCx, JS_NumberValue(size.get_uint64_t())); + + if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "size", number, 0))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + } + + const FileRequestLastModified& lastModified = aMetadata->lastModified(); + if (lastModified.type() != FileRequestLastModified::Tvoid_t) { + MOZ_ASSERT(lastModified.type() == FileRequestLastModified::Tint64_t); + + JS::Rooted<JSObject*> date(aCx, + JS::NewDateObject(aCx, JS::TimeClip(lastModified.get_int64_t()))); + if (NS_WARN_IF(!date)) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + + if (NS_WARN_IF(!JS_DefineProperty(aCx, obj, "lastModified", date, 0))) { + return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + } + } + + aResult.setObject(*obj); + return NS_OK; + } +}; + +already_AddRefed<File> +ConvertActorToFile(FileHandleBase* aFileHandle, + const FileRequestGetFileResponse& aResponse) +{ + auto* actor = static_cast<BlobChild*>(aResponse.fileChild()); + + MutableFileBase* mutableFile = aFileHandle->MutableFile(); + MOZ_ASSERT(mutableFile); + + const FileRequestMetadata& metadata = aResponse.metadata(); + + const FileRequestSize& size = metadata.size(); + MOZ_ASSERT(size.type() == FileRequestSize::Tuint64_t); + + const FileRequestLastModified& lastModified = metadata.lastModified(); + MOZ_ASSERT(lastModified.type() == FileRequestLastModified::Tint64_t); + + actor->SetMysteryBlobInfo(mutableFile->Name(), + mutableFile->Type(), + size.get_uint64_t(), + lastModified.get_int64_t()); + + RefPtr<BlobImpl> blobImpl = actor->GetBlobImpl(); + MOZ_ASSERT(blobImpl); + + RefPtr<File> file = mutableFile->CreateFileFor(blobImpl, aFileHandle); + return file.forget(); +} + +void +HandleSuccess(ResultHelper* aResultHelper) +{ + MOZ_ASSERT(aResultHelper); + + RefPtr<FileRequestBase> fileRequest = aResultHelper->FileRequest(); + MOZ_ASSERT(fileRequest); + fileRequest->AssertIsOnOwningThread(); + + RefPtr<FileHandleBase> fileHandle = aResultHelper->FileHandle(); + MOZ_ASSERT(fileHandle); + + if (fileHandle->IsAborted()) { + fileRequest->SetError(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR); + return; + } + + MOZ_ASSERT(fileHandle->IsOpen()); + + fileRequest->SetResultCallback(aResultHelper); + + MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted()); +} + +void +HandleError(FileRequestBase* aFileRequest, + nsresult aErrorCode, + FileHandleBase* aFileHandle) +{ + MOZ_ASSERT(aFileRequest); + aFileRequest->AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aErrorCode)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_DOM_FILEHANDLE); + MOZ_ASSERT(aFileHandle); + + RefPtr<FileRequestBase> fileRequest = aFileRequest; + RefPtr<FileHandleBase> fileHandle = aFileHandle; + + AutoSetCurrentFileHandle ascfh(aFileHandle); + + fileRequest->SetError(aErrorCode); + + MOZ_ASSERT(fileHandle->IsOpen() || fileHandle->IsAborted()); +} + +} // anonymous namespace + +/******************************************************************************* + * BackgroundMutableFileChildBase + ******************************************************************************/ + +BackgroundMutableFileChildBase::BackgroundMutableFileChildBase( + DEBUGONLY(PRThread* aOwningThread)) + : ThreadObject(DEBUGONLY(aOwningThread)) + , mMutableFile(nullptr) +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BackgroundMutableFileChildBase); +} + +BackgroundMutableFileChildBase::~BackgroundMutableFileChildBase() +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(BackgroundMutableFileChildBase); +} + +void +BackgroundMutableFileChildBase::EnsureDOMObject() +{ + AssertIsOnOwningThread(); + + if (mTemporaryStrongMutableFile) { + return; + } + + mTemporaryStrongMutableFile = CreateMutableFile(); + + MOZ_ASSERT(mTemporaryStrongMutableFile); + mTemporaryStrongMutableFile->AssertIsOnOwningThread(); + + mMutableFile = mTemporaryStrongMutableFile; +} + +void +BackgroundMutableFileChildBase::ReleaseDOMObject() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mTemporaryStrongMutableFile); + mTemporaryStrongMutableFile->AssertIsOnOwningThread(); + MOZ_ASSERT(mMutableFile == mTemporaryStrongMutableFile); + + mTemporaryStrongMutableFile = nullptr; +} + +void +BackgroundMutableFileChildBase::SendDeleteMeInternal() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mTemporaryStrongMutableFile); + + if (mMutableFile) { + mMutableFile->ClearBackgroundActor(); + mMutableFile = nullptr; + + MOZ_ALWAYS_TRUE(PBackgroundMutableFileChild::SendDeleteMe()); + } +} + +void +BackgroundMutableFileChildBase::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + if (mMutableFile) { + mMutableFile->ClearBackgroundActor(); + DEBUGONLY(mMutableFile = nullptr;) + } +} + +PBackgroundFileHandleChild* +BackgroundMutableFileChildBase::AllocPBackgroundFileHandleChild( + const FileMode& aMode) +{ + MOZ_CRASH("PBackgroundFileHandleChild actors should be manually " + "constructed!"); +} + +bool +BackgroundMutableFileChildBase::DeallocPBackgroundFileHandleChild( + PBackgroundFileHandleChild* aActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundFileHandleChild*>(aActor); + return true; +} + +/******************************************************************************* + * BackgroundFileHandleChild + ******************************************************************************/ + +BackgroundFileHandleChild::BackgroundFileHandleChild( + DEBUGONLY(PRThread* aOwningThread,) + FileHandleBase* aFileHandle) + : ThreadObject(DEBUGONLY(aOwningThread)) + , mTemporaryStrongFileHandle(aFileHandle) + , mFileHandle(aFileHandle) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aFileHandle); + aFileHandle->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BackgroundFileHandleChild); +} + +BackgroundFileHandleChild::~BackgroundFileHandleChild() +{ + AssertIsOnOwningThread(); + + MOZ_COUNT_DTOR(BackgroundFileHandleChild); +} + +void +BackgroundFileHandleChild::SendDeleteMeInternal() +{ + AssertIsOnOwningThread(); + + if (mFileHandle) { + NoteActorDestroyed(); + + MOZ_ALWAYS_TRUE(PBackgroundFileHandleChild::SendDeleteMe()); + } +} + +void +BackgroundFileHandleChild::NoteActorDestroyed() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mTemporaryStrongFileHandle, mFileHandle); + + if (mFileHandle) { + mFileHandle->ClearBackgroundActor(); + + // Normally this would be DEBUG-only but NoteActorDestroyed is also called + // from SendDeleteMeInternal. In that case we're going to receive an actual + // ActorDestroy call later and we don't want to touch a dead object. + mTemporaryStrongFileHandle = nullptr; + mFileHandle = nullptr; + } +} + +void +BackgroundFileHandleChild::NoteComplete() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT_IF(mFileHandle, mTemporaryStrongFileHandle); + + mTemporaryStrongFileHandle = nullptr; +} + +void +BackgroundFileHandleChild::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + NoteActorDestroyed(); +} + +bool +BackgroundFileHandleChild::RecvComplete(const bool& aAborted) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mFileHandle); + + mFileHandle->HandleCompleteOrAbort(aAborted); + + NoteComplete(); + return true; +} + +PBackgroundFileRequestChild* +BackgroundFileHandleChild::AllocPBackgroundFileRequestChild( + const FileRequestParams& aParams) +{ + MOZ_CRASH("PBackgroundFileRequestChild actors should be manually " + "constructed!"); +} + +bool +BackgroundFileHandleChild::DeallocPBackgroundFileRequestChild( + PBackgroundFileRequestChild* aActor) +{ + MOZ_ASSERT(aActor); + + delete static_cast<BackgroundFileRequestChild*>(aActor); + return true; +} + +/******************************************************************************* + * BackgroundFileRequestChild + ******************************************************************************/ + +BackgroundFileRequestChild::BackgroundFileRequestChild( + DEBUGONLY(PRThread* aOwningThread,) + FileRequestBase* aFileRequest) + : ThreadObject(DEBUGONLY(aOwningThread)) + , mFileRequest(aFileRequest) + , mFileHandle(aFileRequest->FileHandle()) + , mActorDestroyed(false) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aFileRequest); + aFileRequest->AssertIsOnOwningThread(); + MOZ_ASSERT(mFileHandle); + mFileHandle->AssertIsOnOwningThread(); + + MOZ_COUNT_CTOR(BackgroundFileRequestChild); +} + +BackgroundFileRequestChild::~BackgroundFileRequestChild() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mFileHandle); + + MOZ_COUNT_DTOR(BackgroundFileRequestChild); +} + +void +BackgroundFileRequestChild::HandleResponse(nsresult aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(NS_FAILED(aResponse)); + MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_FILEHANDLE); + MOZ_ASSERT(mFileHandle); + + HandleError(mFileRequest, aResponse, mFileHandle); +} + +void +BackgroundFileRequestChild::HandleResponse( + const FileRequestGetFileResponse& aResponse) +{ + AssertIsOnOwningThread(); + + RefPtr<File> file = ConvertActorToFile(mFileHandle, aResponse); + + ResultHelper helper(mFileRequest, mFileHandle, file); + + HandleSuccess(&helper); +} + +void +BackgroundFileRequestChild::HandleResponse(const nsCString& aResponse) +{ + AssertIsOnOwningThread(); + + ResultHelper helper(mFileRequest, mFileHandle, &aResponse); + + HandleSuccess(&helper); +} + +void +BackgroundFileRequestChild::HandleResponse(const FileRequestMetadata& aResponse) +{ + AssertIsOnOwningThread(); + + ResultHelper helper(mFileRequest, mFileHandle, &aResponse); + + HandleSuccess(&helper); +} + +void +BackgroundFileRequestChild::HandleResponse(JS::Handle<JS::Value> aResponse) +{ + AssertIsOnOwningThread(); + + ResultHelper helper(mFileRequest, mFileHandle, &aResponse); + + HandleSuccess(&helper); +} + +void +BackgroundFileRequestChild::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnOwningThread(); + + MOZ_ASSERT(!mActorDestroyed); + + mActorDestroyed = true; + + if (mFileHandle) { + mFileHandle->AssertIsOnOwningThread(); + + mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ + aWhy == Deletion); + + DEBUGONLY(mFileHandle = nullptr;) + } +} + +bool +BackgroundFileRequestChild::Recv__delete__(const FileRequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mFileRequest); + MOZ_ASSERT(mFileHandle); + + if (mFileHandle->IsAborted()) { + // Always handle an "error" with ABORT_ERR if the file handle was aborted, + // even if the request succeeded or failed with another error. + HandleResponse(NS_ERROR_DOM_FILEHANDLE_ABORT_ERR); + } else { + switch (aResponse.type()) { + case FileRequestResponse::Tnsresult: + HandleResponse(aResponse.get_nsresult()); + break; + + case FileRequestResponse::TFileRequestGetFileResponse: + HandleResponse(aResponse.get_FileRequestGetFileResponse()); + break; + + case FileRequestResponse::TFileRequestReadResponse: + HandleResponse(aResponse.get_FileRequestReadResponse().data()); + break; + + case FileRequestResponse::TFileRequestWriteResponse: + HandleResponse(JS::UndefinedHandleValue); + break; + + case FileRequestResponse::TFileRequestTruncateResponse: + HandleResponse(JS::UndefinedHandleValue); + break; + + case FileRequestResponse::TFileRequestFlushResponse: + HandleResponse(JS::UndefinedHandleValue); + break; + + case FileRequestResponse::TFileRequestGetMetadataResponse: + HandleResponse(aResponse.get_FileRequestGetMetadataResponse() + .metadata()); + break; + + default: + MOZ_CRASH("Unknown response type!"); + } + } + + mFileHandle->OnRequestFinished(/* aActorDestroyedNormally */ true); + + // Null this out so that we don't try to call OnRequestFinished() again in + // ActorDestroy. + mFileHandle = nullptr; + + return true; +} + +bool +BackgroundFileRequestChild::RecvProgress(const uint64_t& aProgress, + const uint64_t& aProgressMax) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mFileRequest); + + mFileRequest->OnProgress(aProgress, aProgressMax); + + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/filehandle/ActorsChild.h b/dom/filehandle/ActorsChild.h new file mode 100644 index 000000000..f2c35e2e1 --- /dev/null +++ b/dom/filehandle/ActorsChild.h @@ -0,0 +1,174 @@ +/* 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_ActorsChild_h +#define mozilla_dom_filehandle_ActorsChild_h + +#include "js/RootingAPI.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/FileHandleCommon.h" +#include "mozilla/dom/PBackgroundFileHandleChild.h" +#include "mozilla/dom/PBackgroundFileRequestChild.h" +#include "mozilla/dom/PBackgroundMutableFileChild.h" + +class nsCString; + +namespace mozilla { +namespace dom { + +class FileHandleBase; +class FileRequestBase; +class MutableFileBase; + +class BackgroundMutableFileChildBase + : public ThreadObject + , public PBackgroundMutableFileChild +{ +protected: + friend class MutableFileBase; + + RefPtr<MutableFileBase> mTemporaryStrongMutableFile; + MutableFileBase* mMutableFile; + +public: + void + EnsureDOMObject(); + + MutableFileBase* + GetDOMObject() const + { + AssertIsOnOwningThread(); + return mMutableFile; + } + + void + ReleaseDOMObject(); + +protected: + BackgroundMutableFileChildBase(DEBUGONLY(PRThread* aOwningThread)); + + ~BackgroundMutableFileChildBase(); + + void + SendDeleteMeInternal(); + + virtual already_AddRefed<MutableFileBase> + CreateMutableFile() = 0; + + // IPDL methods are only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PBackgroundFileHandleChild* + AllocPBackgroundFileHandleChild(const FileMode& aMode) override; + + virtual bool + DeallocPBackgroundFileHandleChild(PBackgroundFileHandleChild* aActor) + override; + + bool + SendDeleteMe() = delete; +}; + +class BackgroundFileHandleChild + : public ThreadObject + , public PBackgroundFileHandleChild +{ + friend class BackgroundMutableFileChildBase; + friend class MutableFileBase; + + // mTemporaryStrongFileHandle is strong and is only valid until the end of + // NoteComplete() member function or until the NoteActorDestroyed() member + // function is called. + RefPtr<FileHandleBase> mTemporaryStrongFileHandle; + + // mFileHandle is weak and is valid until the NoteActorDestroyed() member + // function is called. + FileHandleBase* mFileHandle; + +public: + explicit BackgroundFileHandleChild(DEBUGONLY(PRThread* aOwningThread,) + FileHandleBase* aFileHandle); + + void + SendDeleteMeInternal(); + +private: + ~BackgroundFileHandleChild(); + + void + NoteActorDestroyed(); + + void + NoteComplete(); + + // IPDL methods are only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + bool + RecvComplete(const bool& aAborted) override; + + virtual PBackgroundFileRequestChild* + AllocPBackgroundFileRequestChild(const FileRequestParams& aParams) + override; + + virtual bool + DeallocPBackgroundFileRequestChild(PBackgroundFileRequestChild* aActor) + override; + + bool + SendDeleteMe() = delete; +}; + +class BackgroundFileRequestChild final + : public ThreadObject + , public PBackgroundFileRequestChild +{ + friend class BackgroundFileHandleChild; + friend class FileHandleBase; + + RefPtr<FileRequestBase> mFileRequest; + RefPtr<FileHandleBase> mFileHandle; + bool mActorDestroyed; + +private: + // Only created by FileHandleBase. + explicit BackgroundFileRequestChild(DEBUGONLY(PRThread* aOwningThread,) + FileRequestBase* aFileRequest); + + // Only destroyed by BackgroundFileHandleChild. + ~BackgroundFileRequestChild(); + + void + HandleResponse(nsresult aResponse); + + void + HandleResponse(const FileRequestGetFileResponse& aResponse); + + void + HandleResponse(const nsCString& aResponse); + + void + HandleResponse(const FileRequestMetadata& aResponse); + + void + HandleResponse(JS::Handle<JS::Value> aResponse); + + // IPDL methods are only called by IPDL. + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__(const FileRequestResponse& aResponse) override; + + virtual bool + RecvProgress(const uint64_t& aProgress, + const uint64_t& aProgressMax) override; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_filehandle_ActorsChild_h diff --git a/dom/filehandle/ActorsParent.cpp b/dom/filehandle/ActorsParent.cpp new file mode 100644 index 000000000..6b81c782f --- /dev/null +++ b/dom/filehandle/ActorsParent.cpp @@ -0,0 +1,2666 @@ +/* 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/Unused.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FileHandleCommon.h" +#include "mozilla/dom/PBackgroundFileHandleParent.h" +#include "mozilla/dom/PBackgroundFileRequestParent.h" +#include "mozilla/dom/indexedDB/ActorsParent.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsIEventTarget.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" + +#define DISABLE_ASSERTS_FOR_FUZZING 0 + +#if DISABLE_ASSERTS_FOR_FUZZING +#define ASSERT_UNLESS_FUZZING(...) do { } while (0) +#else +#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) +#endif + +namespace mozilla { +namespace dom { + +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() {} + + 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; + nsTHashtable<nsStringHashKey> mFilesReading; + nsTHashtable<nsStringHashKey> 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.PutEntry(aFileName); + } + + void + LockFileForWriting(const nsAString& aFileName) + { + mFilesWriting.PutEntry(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 nsAutoPtr<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; + + DEBUGONLY(nsCOMPtr<nsIEventTarget> mThreadPoolEventTarget;) + +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 bool + RecvDeleteMe() override; + + virtual bool + RecvFinish() override; + + virtual bool + RecvAbort() override; + + virtual PBackgroundFileRequestParent* + AllocPBackgroundFileRequestParent(const FileRequestParams& aParams) override; + + virtual bool + RecvPBackgroundFileRequestConstructor(PBackgroundFileRequestParent* aActor, + const FileRequestParams& aParams) + override; + + virtual bool + DeallocPBackgroundFileRequestParent(PBackgroundFileRequestParent* aActor) + override; +}; + +class FileHandleOp +{ +protected: + nsCOMPtr<nsIEventTarget> mOwningThread; + RefPtr<FileHandle> mFileHandle; + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileHandleOp) + + void + AssertIsOnOwningThread() const + { + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mOwningThread); + DebugOnly<bool> current; + MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(¤t))); + MOZ_ASSERT(current); + } + + nsIEventTarget* + OwningThread() const + { + return mOwningThread; + } + + void + AssertIsOnThreadPool() const + { + MOZ_ASSERT(mFileHandle); + mFileHandle->AssertIsOnThreadPool(); + } + + void + Enqueue(); + + virtual void + RunOnThreadPool() = 0; + + virtual void + RunOnOwningThread() = 0; + +protected: + FileHandleOp(FileHandle* aFileHandle) + : mOwningThread(NS_GetCurrentThread()) + , mFileHandle(aFileHandle) + { + AssertIsOnOwningThread(); + MOZ_ASSERT(aFileHandle); + } + + virtual + ~FileHandleOp() + { } +}; + +class FileHandle::FinishOp + : public FileHandleOp +{ + friend class FileHandle; + + bool mAborted; + +private: + FinishOp(FileHandle* aFileHandle, + bool aAborted) + : FileHandleOp(aFileHandle) + , mAborted(aAborted) + { + MOZ_ASSERT(aFileHandle); + } + + ~FinishOp() + { } + + virtual void + RunOnThreadPool() override; + + virtual void + RunOnOwningThread() override; +}; + +class NormalFileHandleOp + : public FileHandleOp + , public PBackgroundFileRequestParent +{ + nsresult mResultCode; + Atomic<bool> mOperationMayProceed; + bool mActorDestroyed; + const bool mFileHandleIsAborted; + + DEBUGONLY(bool mResponseSent;) + +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()) + DEBUGONLY(, mResponseSent(false)) + { + 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) + : mCopyFileHandleOp(aCopyFileHandleOp) + , mProgress(aProgress) + , mProgressMax(aProgressMax) + { } + +private: + ~ProgressRunnable() {} + + 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() + { } + + virtual nsresult + DoFileWork(FileHandle* aFileHandle) override; + + virtual void + GetResponse(FileRequestResponse& aResponse) override; +}; + +class ReadOp final + : public CopyFileHandleOp +{ + friend class FileHandle; + + class MemoryOutputStream; + + const FileRequestReadParams mParams; + +private: + // Only created by FileHandle. + ReadOp(FileHandle* aFileHandle, + const FileRequestParams& aParams); + + ~ReadOp() + { } + + virtual bool + Init(FileHandle* aFileHandle) override; + + virtual void + GetResponse(FileRequestResponse& aResponse) override; +}; + +class ReadOp::MemoryOutputStream final + : public nsIOutputStream +{ + nsCString mData; + uint64_t mOffset; + +public: + static already_AddRefed<MemoryOutputStream> + Create(uint64_t aSize); + + const nsCString& + Data() const + { + return mData; + } + +private: + MemoryOutputStream() + : mOffset(0) + { } + + virtual ~MemoryOutputStream() + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM +}; + +class WriteOp final + : public CopyFileHandleOp +{ + friend class FileHandle; + + const FileRequestWriteParams mParams; + +private: + // Only created by FileHandle. + WriteOp(FileHandle* aFileHandle, + const FileRequestParams& aParams); + + ~WriteOp() + { } + + 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() + { } + + 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() + { } + + virtual nsresult + DoFileWork(FileHandle* aFileHandle) override; + + virtual void + GetResponse(FileRequestResponse& aResponse) override; +}; + +class GetFileOp final + : public GetMetadataOp +{ + friend class FileHandle; + + PBackgroundParent* mBackgroundParent; + +private: + // Only created by FileHandle. + GetFileOp(FileHandle* aFileHandle, + const FileRequestParams& aParams); + + ~GetFileOp() + { } + + 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!"); + } +} + +} // namespace + +/******************************************************************************* + * FileHandleThreadPool implementation + ******************************************************************************/ + +FileHandleThreadPool::FileHandleThreadPool() + : mOwningThread(NS_GetCurrentThread()) + , mShutdownRequested(false) + , mShutdownComplete(false) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mOwningThread); + 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(mOwningThread); + + bool current; + MOZ_ALWAYS_SUCCEEDS(mOwningThread->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; + if (!mDirectoryInfos.Get(directoryId, &directoryInfo)) { + nsAutoPtr<DirectoryInfo> newDirectoryInfo(new DirectoryInfo(this)); + + mDirectoryInfos.Put(directoryId, newDirectoryInfo); + + directoryInfo = newDirectoryInfo.forget(); + } + + 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); + + nsAutoPtr<StoragesCompleteCallback> callback( + new StoragesCompleteCallback(Move(aDirectoryIds), aCallback)); + + if (!MaybeFireCallback(callback)) { + mCompleteCallbacks.AppendElement(callback.forget()); + } +} + +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; + } + + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + + while (!mShutdownComplete) { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread)); + } +} + +nsresult +FileHandleThreadPool::Init() +{ + AssertIsOnOwningThread(); + + mThreadPool = new nsThreadPool(); + + nsresult rv = mThreadPool->SetName(NS_LITERAL_CSTRING("FileHandles")); + 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++) { + nsAutoPtr<StoragesCompleteCallback> completeCallback( + mCompleteCallbacks[index].forget()); + 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. + uint32_t index = 0; + while (index < mCompleteCallbacks.Length()) { + if (MaybeFireCallback(mCompleteCallbacks[index])) { + mCompleteCallbacks.RemoveElementAt(index); + } + else { + index++; + } + } + + 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) + : 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; + delayedEnqueueInfos.SwapElements(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(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(nsTHashtable<nsPtrHashKey<FileHandle>>& aTable) + { + AssertIsOnBackgroundThread(); + + const uint32_t count = aTable.Count(); + if (!count) { + return true; + } + + FallibleTArray<RefPtr<FileHandle>> fileHandles; + if (NS_WARN_IF(!fileHandles.SetCapacity(count, fallible))) { + return false; + } + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + if (NS_WARN_IF(!fileHandles.AppendElement(iter.Get()->GetKey(), + fallible))) { + return false; + } + } + + if (count) { + for (uint32_t index = 0; index < count; index++) { + RefPtr<FileHandle> fileHandle = fileHandles[index].forget(); + 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.GetEntry(aFileHandle)); + MOZ_ASSERT(!mInvalidated); + + if (NS_WARN_IF(!mFileHandles.PutEntry(aFileHandle, fallible))) { + return false; + } + + if (mFileHandles.Count() == 1) { + NoteActiveState(); + } + + return true; +} + +void +BackgroundMutableFileParentBase::UnregisterFileHandle(FileHandle* aFileHandle) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileHandle); + MOZ_ASSERT(mFileHandles.GetEntry(aFileHandle)); + + mFileHandles.RemoveEntry(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<nsIFileStream> stream; + rv = NS_NewLocalFileStream(getter_AddRefs(stream), mFile, -1, -1, + nsIFileStream::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)) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + RefPtr<FileHandle> fileHandle = new FileHandle(this, aMode); + + return fileHandle.forget().take(); +} + +bool +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 true; + } + + return true; +} + +bool +BackgroundMutableFileParentBase::DeallocPBackgroundFileHandleParent( + PBackgroundFileHandleParent* aActor) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aActor); + + RefPtr<FileHandle> fileHandle = + dont_AddRef(static_cast<FileHandle*>(aActor)); + return true; +} + +bool +BackgroundMutableFileParentBase::RecvDeleteMe() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mActorDestroyed); + + return PBackgroundMutableFileParent::Send__delete__(this); +} + +bool +BackgroundMutableFileParentBase::RecvGetFileId(int64_t* aFileId) +{ + AssertIsOnBackgroundThread(); + + *aFileId = -1; + return true; +} + +/******************************************************************************* + * 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())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + break; + } + + case FileRequestParams::TFileRequestReadParams: { + const FileRequestReadParams& params = + aParams.get_FileRequestReadParams(); + + if (NS_WARN_IF(params.offset() == UINT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (NS_WARN_IF(!params.size())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + break; + } + + case FileRequestParams::TFileRequestWriteParams: { + if (NS_WARN_IF(mMode != FileMode::Readwrite)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + const FileRequestWriteParams& params = + aParams.get_FileRequestWriteParams(); + + + if (NS_WARN_IF(!params.dataLength())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (NS_WARN_IF(!VerifyRequestData(params.data()))) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + break; + } + + case FileRequestParams::TFileRequestTruncateParams: { + if (NS_WARN_IF(mMode != FileMode::Readwrite)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + const FileRequestTruncateParams& params = + aParams.get_FileRequestTruncateParams(); + + if (NS_WARN_IF(params.offset() == UINT64_MAX)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + break; + } + + case FileRequestParams::TFileRequestFlushParams: { + if (NS_WARN_IF(mMode != FileMode::Readwrite)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + break; + } + + case FileRequestParams::TFileRequestGetFileParams: { + 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())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + break; + } + + case FileRequestData::TFileRequestBlobData: { + const FileRequestBlobData& data = + aData.get_FileRequestBlobData(); + + if (NS_WARN_IF(data.blobChild())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + if (NS_WARN_IF(!data.blobParent())) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + 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(); + } +} + +bool +FileHandle::RecvDeleteMe() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!IsActorDestroyed()); + + return PBackgroundFileHandleParent::Send__delete__(this); +} + +bool +FileHandle::RecvFinish() +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(mFinishOrAbortReceived)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + mFinishOrAbortReceived = true; + + MaybeFinishOrAbort(); + return true; +} + +bool +FileHandle::RecvAbort() +{ + AssertIsOnBackgroundThread(); + + if (NS_WARN_IF(mFinishOrAbortReceived)) { + ASSERT_UNLESS_FUZZING(); + return false; + } + + mFinishOrAbortReceived = true; + + Abort(/* aForce */ false); + return true; +} + +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))) { + ASSERT_UNLESS_FUZZING(); + return nullptr; + } + + if (NS_WARN_IF(mFinishOrAbortReceived)) { + ASSERT_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; + + case FileRequestParams::TFileRequestGetFileParams: + actor = new GetFileOp(this, aParams); + break; + + default: + MOZ_CRASH("Should never get here!"); + } + + MOZ_ASSERT(actor); + + // Transfer ownership to IPDL. + return actor.forget().take(); +} + +bool +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 false; + } + + op->Enqueue(); + return true; +} + +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); + + 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(!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; + } + } + + DEBUGONLY(mResponseSent = true;) + + return NS_OK; +} + +bool +NormalFileHandleOp::SendFailureResult(nsresult aResultCode) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(NS_FAILED(aResultCode)); + + bool result = false; + + if (!IsActorDestroyed()) { + result = + PBackgroundFileRequestParent::Send__delete__(this, aResultCode); + } + + DEBUGONLY(mResponseSent = true;) + + 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); + + mOwningThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + } while (true); + + 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() = uint64_t(size); + } else { + mMetadata.size() = void_t(); + } + + if (mParams.lastModified()) { + int64_t lastModified; + rv = metadata->GetLastModified(&lastModified); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMetadata.lastModified() = lastModified; + } else { + mMetadata.lastModified() = void_t(); + } + + 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; + } + + mBufferStream = MemoryOutputStream::Create(mParams.size()); + 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<MemoryOutputStream*>(mBufferStream.get()); + + aResponse = FileRequestReadResponse(stream->Data()); +} + +// static +already_AddRefed<ReadOp::MemoryOutputStream> +ReadOp:: +MemoryOutputStream::Create(uint64_t aSize) +{ + MOZ_ASSERT(aSize, "Passed zero size!"); + + if (NS_WARN_IF(aSize > UINT32_MAX)) { + return nullptr; + } + + RefPtr<MemoryOutputStream> stream = new MemoryOutputStream(); + + char* dummy; + uint32_t length = stream->mData.GetMutableData(&dummy, aSize, fallible); + if (NS_WARN_IF(length != aSize)) { + return nullptr; + } + + return stream.forget(); +} + +NS_IMPL_ISUPPORTS(ReadOp::MemoryOutputStream, nsIOutputStream) + +NS_IMETHODIMP +ReadOp:: +MemoryOutputStream::Close() +{ + mData.Truncate(mOffset); + return NS_OK; +} + +NS_IMETHODIMP +ReadOp:: +MemoryOutputStream::Write(const char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return WriteSegments(NS_CopySegmentToBuffer, (char*)aBuf, aCount, _retval); +} + +NS_IMETHODIMP +ReadOp:: +MemoryOutputStream::Flush() +{ + return NS_OK; +} + +NS_IMETHODIMP +ReadOp:: +MemoryOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +ReadOp:: +MemoryOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) +{ + NS_ASSERTION(mData.Length() >= mOffset, "Bad stream state!"); + + uint32_t maxCount = mData.Length() - mOffset; + if (maxCount == 0) { + *_retval = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + nsresult rv = aReader(this, aClosure, mData.BeginWriting() + mOffset, 0, + aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, + "Reader should not read more than we asked it to read!"); + mOffset += *_retval; + } + + return NS_OK; +} + +NS_IMETHODIMP +ReadOp:: +MemoryOutputStream::IsNonBlocking(bool* _retval) +{ + *_retval = false; + return NS_OK; +} + +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(); + + auto blobActor = static_cast<BlobParent*>(blobData.blobParent()); + + RefPtr<BlobImpl> blobImpl = blobActor->GetBlobImpl(); + + ErrorResult rv; + blobImpl->GetInternalStream(getter_AddRefs(inputStream), rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + 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); + + 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(); +} + +GetFileOp::GetFileOp(FileHandle* aFileHandle, + const FileRequestParams& aParams) + : GetMetadataOp(aFileHandle, + FileRequestGetMetadataParams(true, true)) + , mBackgroundParent(aFileHandle->GetBackgroundParent()) +{ + MOZ_ASSERT(aParams.type() == FileRequestParams::TFileRequestGetFileParams); + MOZ_ASSERT(mBackgroundParent); +} + +void +GetFileOp::GetResponse(FileRequestResponse& aResponse) +{ + AssertIsOnOwningThread(); + + RefPtr<BlobImpl> blobImpl = mFileHandle->GetMutableFile()->CreateBlobImpl(); + MOZ_ASSERT(blobImpl); + + PBlobParent* actor = + BackgroundParent::GetOrCreateActorForBlobImpl(mBackgroundParent, blobImpl); + if (NS_WARN_IF(!actor)) { + // This can only fail if the child has crashed. + aResponse = NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR; + return; + } + + FileRequestGetFileResponse response; + response.fileParent() = actor; + response.metadata() = mMetadata; + + aResponse = response; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/filehandle/ActorsParent.h b/dom/filehandle/ActorsParent.h new file mode 100644 index 000000000..5d251715e --- /dev/null +++ b/dom/filehandle/ActorsParent.h @@ -0,0 +1,221 @@ +/* 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 "nsAutoPtr.h" +#include "nsClassHashtable.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsString.h" +#include "nsTArrayForwardDeclare.h" +#include "nsTHashtable.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> mOwningThread; + + nsClassHashtable<nsCStringHashKey, DirectoryInfo> mDirectoryInfos; + + nsTArray<nsAutoPtr<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 +{ + nsTHashtable<nsPtrHashKey<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) override; + + virtual bool + RecvPBackgroundFileHandleConstructor(PBackgroundFileHandleParent* aActor, + const FileMode& aMode) override; + + virtual bool + DeallocPBackgroundFileHandleParent(PBackgroundFileHandleParent* aActor) + override; + + virtual bool + RecvDeleteMe() override; + + virtual bool + RecvGetFileId(int64_t* aFileId) override; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_filehandle_ActorsParent_h diff --git a/dom/filehandle/FileHandleBase.cpp b/dom/filehandle/FileHandleBase.cpp new file mode 100644 index 000000000..ebfd6f077 --- /dev/null +++ b/dom/filehandle/FileHandleBase.cpp @@ -0,0 +1,636 @@ +/* -*- 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 "FileHandleBase.h" + +#include "ActorsChild.h" +#include "BackgroundChildImpl.h" +#include "FileRequestBase.h" +#include "mozilla/Assertions.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/PBackgroundFileHandle.h" +#include "mozilla/dom/UnionConversions.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "MutableFileBase.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsString.h" + +namespace mozilla { +namespace dom { + +using namespace mozilla::ipc; + +FileHandleBase::FileHandleBase(DEBUGONLY(PRThread* aOwningThread,) + FileMode aMode) + : RefCountedThreadObject(DEBUGONLY(aOwningThread)) + , mBackgroundActor(nullptr) + , mLocation(0) + , mPendingRequestCount(0) + , mReadyState(INITIAL) + , mMode(aMode) + , mAborted(false) + , mCreating(false) + DEBUGONLY(, mSentFinishOrAbort(false)) + DEBUGONLY(, mFiredCompleteOrAbort(false)) +{ + AssertIsOnOwningThread(); +} + +FileHandleBase::~FileHandleBase() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mPendingRequestCount); + MOZ_ASSERT(!mCreating); + MOZ_ASSERT(mSentFinishOrAbort); + MOZ_ASSERT_IF(mBackgroundActor, mFiredCompleteOrAbort); + + if (mBackgroundActor) { + mBackgroundActor->SendDeleteMeInternal(); + + MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!"); + } +} + +// static +FileHandleBase* +FileHandleBase::GetCurrent() +{ + MOZ_ASSERT(BackgroundChild::GetForCurrentThread()); + + BackgroundChildImpl::ThreadLocal* threadLocal = + BackgroundChildImpl::GetThreadLocalForCurrentThread(); + MOZ_ASSERT(threadLocal); + + return threadLocal->mCurrentFileHandle; +} + +void +FileHandleBase::SetBackgroundActor(BackgroundFileHandleChild* aActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); + MOZ_ASSERT(!mBackgroundActor); + + mBackgroundActor = aActor; +} + +void +FileHandleBase::StartRequest(FileRequestBase* aFileRequest, + const FileRequestParams& aParams) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aFileRequest); + MOZ_ASSERT(aParams.type() != FileRequestParams::T__None); + + BackgroundFileRequestChild* actor = + new BackgroundFileRequestChild(DEBUGONLY(mBackgroundActor->OwningThread(),) + aFileRequest); + + mBackgroundActor->SendPBackgroundFileRequestConstructor(actor, aParams); + + // Balanced in BackgroundFileRequestChild::Recv__delete__(). + OnNewRequest(); +} + +void +FileHandleBase::OnNewRequest() +{ + AssertIsOnOwningThread(); + + if (!mPendingRequestCount) { + MOZ_ASSERT(mReadyState == INITIAL); + mReadyState = LOADING; + } + + ++mPendingRequestCount; +} + +void +FileHandleBase::OnRequestFinished(bool aActorDestroyedNormally) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mPendingRequestCount); + + --mPendingRequestCount; + + if (!mPendingRequestCount && !MutableFile()->IsInvalidated()) { + mReadyState = FINISHING; + + if (aActorDestroyedNormally) { + if (!mAborted) { + SendFinish(); + } else { + SendAbort(); + } + } else { + // Don't try to send any more messages to the parent if the request actor + // was killed. +#ifdef DEBUG + MOZ_ASSERT(!mSentFinishOrAbort); + mSentFinishOrAbort = true; +#endif + } + } +} + +bool +FileHandleBase::IsOpen() const +{ + AssertIsOnOwningThread(); + + // If we haven't started anything then we're open. + if (mReadyState == INITIAL) { + return true; + } + + // If we've already started then we need to check to see if we still have the + // mCreating flag set. If we do (i.e. we haven't returned to the event loop + // from the time we were created) then we are open. Otherwise check the + // currently running file handles to see if it's the same. We only allow other + // requests to be made if this file handle is currently running. + if (mReadyState == LOADING && (mCreating || GetCurrent() == this)) { + return true; + } + + return false; +} + +void +FileHandleBase::Abort() +{ + AssertIsOnOwningThread(); + + if (IsFinishingOrDone()) { + // Already started (and maybe finished) the finish or abort so there is + // nothing to do here. + return; + } + + const bool isInvalidated = MutableFile()->IsInvalidated(); + bool needToSendAbort = mReadyState == INITIAL && !isInvalidated; + +#ifdef DEBUG + if (isInvalidated) { + mSentFinishOrAbort = true; + } +#endif + + mAborted = true; + mReadyState = DONE; + + // Fire the abort event if there are no outstanding requests. Otherwise the + // abort event will be fired when all outstanding requests finish. + if (needToSendAbort) { + SendAbort(); + } +} + +already_AddRefed<FileRequestBase> +FileHandleBase::Read(uint64_t aSize, bool aHasEncoding, + const nsAString& aEncoding, ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State and argument checking for read + if (!CheckStateAndArgumentsForRead(aSize, aRv)) { + return nullptr; + } + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + FileRequestReadParams params; + params.offset() = mLocation; + params.size() = aSize; + + RefPtr<FileRequestBase> fileRequest = GenerateFileRequest(); + if (aHasEncoding) { + fileRequest->SetEncoding(aEncoding); + } + + StartRequest(fileRequest, params); + + mLocation += aSize; + + return fileRequest.forget(); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::Truncate(const Optional<uint64_t>& aSize, ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State checking for write + if (!CheckStateForWrite(aRv)) { + return nullptr; + } + + // Getting location and additional state checking for truncate + uint64_t location; + if (aSize.WasPassed()) { + // Just in case someone calls us from C++ + MOZ_ASSERT(aSize.Value() != UINT64_MAX, "Passed wrong size!"); + location = aSize.Value(); + } else { + if (mLocation == UINT64_MAX) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); + return nullptr; + } + location = mLocation; + } + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + FileRequestTruncateParams params; + params.offset() = location; + + RefPtr<FileRequestBase> fileRequest = GenerateFileRequest(); + + StartRequest(fileRequest, params); + + if (aSize.WasPassed()) { + mLocation = aSize.Value(); + } + + return fileRequest.forget(); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::Flush(ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State checking for write + if (!CheckStateForWrite(aRv)) { + return nullptr; + } + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + FileRequestFlushParams params; + + RefPtr<FileRequestBase> fileRequest = GenerateFileRequest(); + + StartRequest(fileRequest, params); + + return fileRequest.forget(); +} + +void +FileHandleBase::Abort(ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // This method is special enough for not using generic state checking methods. + + if (IsFinishingOrDone()) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); + return; + } + + Abort(); +} + +void +FileHandleBase::HandleCompleteOrAbort(bool aAborted) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mFiredCompleteOrAbort); + + mReadyState = DONE; + + DEBUGONLY(mFiredCompleteOrAbort = true;) +} + +void +FileHandleBase::OnReturnToEventLoop() +{ + AssertIsOnOwningThread(); + + // We're back at the event loop, no longer newborn. + mCreating = false; + + // Maybe finish if there were no requests generated. + if (mReadyState == INITIAL) { + mReadyState = DONE; + + SendFinish(); + } +} + +bool +FileHandleBase::CheckState(ErrorResult& aRv) +{ + if (!IsOpen()) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR); + return false; + } + + return true; +} + +bool +FileHandleBase::CheckStateAndArgumentsForRead(uint64_t aSize, ErrorResult& aRv) +{ + // Common state checking + if (!CheckState(aRv)) { + return false; + } + + // Additional state checking for read + if (mLocation == UINT64_MAX) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); + return false; + } + + // Argument checking for read + if (!aSize) { + aRv.ThrowTypeError<MSG_INVALID_READ_SIZE>(); + return false; + } + + return true; +} + +bool +FileHandleBase::CheckStateForWrite(ErrorResult& aRv) +{ + // Common state checking + if (!CheckState(aRv)) { + return false; + } + + // Additional state checking for write + if (mMode != FileMode::Readwrite) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR); + return false; + } + + return true; +} + +bool +FileHandleBase::CheckStateForWriteOrAppend(bool aAppend, ErrorResult& aRv) +{ + // State checking for write + if (!CheckStateForWrite(aRv)) { + return false; + } + + // Additional state checking for write + if (!aAppend && mLocation == UINT64_MAX) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); + return false; + } + + return true; +} + +already_AddRefed<FileRequestBase> +FileHandleBase::WriteOrAppend( + const StringOrArrayBufferOrArrayBufferViewOrBlob& aValue, + bool aAppend, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + if (aValue.IsString()) { + return WriteOrAppend(aValue.GetAsString(), aAppend, aRv); + } + + if (aValue.IsArrayBuffer()) { + return WriteOrAppend(aValue.GetAsArrayBuffer(), aAppend, aRv); + } + + if (aValue.IsArrayBufferView()) { + return WriteOrAppend(aValue.GetAsArrayBufferView(), aAppend, aRv); + } + + MOZ_ASSERT(aValue.IsBlob()); + return WriteOrAppend(aValue.GetAsBlob(), aAppend, aRv); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::WriteOrAppend(const nsAString& aValue, + bool aAppend, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State checking for write or append + if (!CheckStateForWriteOrAppend(aAppend, aRv)) { + return nullptr; + } + + NS_ConvertUTF16toUTF8 cstr(aValue); + + uint64_t dataLength = cstr.Length();; + if (!dataLength) { + return nullptr; + } + + FileRequestStringData stringData(cstr); + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + return WriteInternal(stringData, dataLength, aAppend, aRv); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::WriteOrAppend(const ArrayBuffer& aValue, + bool aAppend, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State checking for write or append + if (!CheckStateForWriteOrAppend(aAppend, aRv)) { + return nullptr; + } + + aValue.ComputeLengthAndData(); + + uint64_t dataLength = aValue.Length();; + if (!dataLength) { + return nullptr; + } + + const char* data = reinterpret_cast<const char*>(aValue.Data()); + + FileRequestStringData stringData; + if (NS_WARN_IF(!stringData.string().Assign(data, aValue.Length(), + fallible_t()))) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); + return nullptr; + } + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + return WriteInternal(stringData, dataLength, aAppend, aRv); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::WriteOrAppend(const ArrayBufferView& aValue, + bool aAppend, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State checking for write or append + if (!CheckStateForWriteOrAppend(aAppend, aRv)) { + return nullptr; + } + + aValue.ComputeLengthAndData(); + + uint64_t dataLength = aValue.Length();; + if (!dataLength) { + return nullptr; + } + + const char* data = reinterpret_cast<const char*>(aValue.Data()); + + FileRequestStringData stringData; + if (NS_WARN_IF(!stringData.string().Assign(data, aValue.Length(), + fallible_t()))) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); + return nullptr; + } + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + return WriteInternal(stringData, dataLength, aAppend, aRv); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::WriteOrAppend(Blob& aValue, + bool aAppend, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + // State checking for write or append + if (!CheckStateForWriteOrAppend(aAppend, aRv)) { + return nullptr; + } + + ErrorResult rv; + uint64_t dataLength = aValue.GetSize(rv); + if (NS_WARN_IF(rv.Failed())) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); + return nullptr; + } + + if (!dataLength) { + return nullptr; + } + + PBackgroundChild* backgroundActor = BackgroundChild::GetForCurrentThread(); + MOZ_ASSERT(backgroundActor); + + PBlobChild* blobActor = + BackgroundChild::GetOrCreateActorForBlob(backgroundActor, &aValue); + if (NS_WARN_IF(!blobActor)) { + aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); + return nullptr; + } + + FileRequestBlobData blobData; + blobData.blobChild() = blobActor; + + // Do nothing if the window is closed + if (!CheckWindow()) { + return nullptr; + } + + return WriteInternal(blobData, dataLength, aAppend, aRv); +} + +already_AddRefed<FileRequestBase> +FileHandleBase::WriteInternal(const FileRequestData& aData, + uint64_t aDataLength, + bool aAppend, + ErrorResult& aRv) +{ + AssertIsOnOwningThread(); + + DebugOnly<ErrorResult> error; + MOZ_ASSERT(CheckStateForWrite(error)); + MOZ_ASSERT_IF(!aAppend, mLocation != UINT64_MAX); + MOZ_ASSERT(aDataLength); + MOZ_ASSERT(CheckWindow()); + + FileRequestWriteParams params; + params.offset() = aAppend ? UINT64_MAX : mLocation; + params.data() = aData; + params.dataLength() = aDataLength; + + RefPtr<FileRequestBase> fileRequest = GenerateFileRequest(); + MOZ_ASSERT(fileRequest); + + StartRequest(fileRequest, params); + + if (aAppend) { + mLocation = UINT64_MAX; + } + else { + mLocation += aDataLength; + } + + return fileRequest.forget(); +} + +void +FileHandleBase::SendFinish() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(!mAborted); + MOZ_ASSERT(IsFinishingOrDone()); + MOZ_ASSERT(!mSentFinishOrAbort); + MOZ_ASSERT(!mPendingRequestCount); + + MOZ_ASSERT(mBackgroundActor); + mBackgroundActor->SendFinish(); + + DEBUGONLY(mSentFinishOrAbort = true;) +} + +void +FileHandleBase::SendAbort() +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(mAborted); + MOZ_ASSERT(IsFinishingOrDone()); + MOZ_ASSERT(!mSentFinishOrAbort); + + MOZ_ASSERT(mBackgroundActor); + mBackgroundActor->SendAbort(); + + DEBUGONLY(mSentFinishOrAbort = true;) +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/filehandle/FileHandleBase.h b/dom/filehandle/FileHandleBase.h new file mode 100644 index 000000000..859a6e9ae --- /dev/null +++ b/dom/filehandle/FileHandleBase.h @@ -0,0 +1,246 @@ +/* -*- 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_h +#define mozilla_dom_FileHandle_h + +#include "FileHandleCommon.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/FileModeBinding.h" +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/TypedArray.h" + +template <class> struct already_AddRefed; +class nsAString; +struct PRThread; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class BackgroundFileHandleChild; +class Blob; +class FileRequestBase; +class FileRequestData; +class FileRequestParams; +class MutableFileBase; +class StringOrArrayBufferOrArrayBufferViewOrBlob; + +/** + * This class provides a base for FileHandle implementations. + */ +class FileHandleBase + : public RefCountedThreadObject +{ +public: + enum ReadyState + { + INITIAL = 0, + LOADING, + FINISHING, + DONE + }; + +private: + BackgroundFileHandleChild* mBackgroundActor; + + uint64_t mLocation; + + uint32_t mPendingRequestCount; + + ReadyState mReadyState; + FileMode mMode; + + bool mAborted; + bool mCreating; + + DEBUGONLY(bool mSentFinishOrAbort;) + DEBUGONLY(bool mFiredCompleteOrAbort;) + +public: + static FileHandleBase* + GetCurrent(); + + void + SetBackgroundActor(BackgroundFileHandleChild* aActor); + + void + ClearBackgroundActor() + { + AssertIsOnOwningThread(); + + mBackgroundActor = nullptr; + } + + void + StartRequest(FileRequestBase* aFileRequest, const FileRequestParams& aParams); + + void + OnNewRequest(); + + void + OnRequestFinished(bool aActorDestroyedNormally); + + bool + IsOpen() const; + + bool + IsFinishingOrDone() const + { + AssertIsOnOwningThread(); + + return mReadyState == FINISHING || mReadyState == DONE; + } + + bool + IsDone() const + { + AssertIsOnOwningThread(); + + return mReadyState == DONE; + } + + bool + IsAborted() const + { + AssertIsOnOwningThread(); + return mAborted; + } + + void + SetCreating() + { + mCreating = true; + } + + void + Abort(); + + // Shared WebIDL (IndexedDB FileHandle and FileSystem FileHandle) + FileMode + Mode() const + { + AssertIsOnOwningThread(); + return mMode; + } + + bool + Active() const + { + AssertIsOnOwningThread(); + return IsOpen(); + } + + Nullable<uint64_t> + GetLocation() const + { + AssertIsOnOwningThread(); + + if (mLocation == UINT64_MAX) { + return Nullable<uint64_t>(); + } + + return Nullable<uint64_t>(mLocation); + } + + void + SetLocation(const Nullable<uint64_t>& aLocation) + { + AssertIsOnOwningThread(); + + // Null means the end-of-file. + if (aLocation.IsNull()) { + mLocation = UINT64_MAX; + } else { + mLocation = aLocation.Value(); + } + } + + already_AddRefed<FileRequestBase> + Read(uint64_t aSize, bool aHasEncoding, const nsAString& aEncoding, + ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + Truncate(const Optional<uint64_t>& aSize, ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + Flush(ErrorResult& aRv); + + void + Abort(ErrorResult& aRv); + + // Must be overridden in subclasses. + virtual MutableFileBase* + MutableFile() const = 0; + + // May be overridden in subclasses. + virtual void + HandleCompleteOrAbort(bool aAborted); + +protected: + FileHandleBase(DEBUGONLY(PRThread* aOwningThread,) + FileMode aMode); + + ~FileHandleBase(); + + void + OnReturnToEventLoop(); + + bool + CheckState(ErrorResult& aRv); + + bool + CheckStateAndArgumentsForRead(uint64_t aSize, ErrorResult& aRv); + + bool + CheckStateForWrite(ErrorResult& aRv); + + bool + CheckStateForWriteOrAppend(bool aAppend, ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + WriteOrAppend(const StringOrArrayBufferOrArrayBufferViewOrBlob& aValue, + bool aAppend, + ErrorResult& aRv); + + // Must be overridden in subclasses. + virtual bool + CheckWindow() = 0; + + // Must be overridden in subclasses. + virtual already_AddRefed<FileRequestBase> + GenerateFileRequest() = 0; + +private: + already_AddRefed<FileRequestBase> + WriteOrAppend(const nsAString& aValue, bool aAppend, ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + WriteOrAppend(const ArrayBuffer& aValue, bool aAppend, ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + WriteOrAppend(const ArrayBufferView& aValue, bool aAppend, ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + WriteOrAppend(Blob& aValue, bool aAppend, ErrorResult& aRv); + + already_AddRefed<FileRequestBase> + WriteInternal(const FileRequestData& aData, uint64_t aDataLength, + bool aAppend, ErrorResult& aRv); + + void + SendFinish(); + + void + SendAbort(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FileHandle_h diff --git a/dom/filehandle/FileHandleCommon.cpp b/dom/filehandle/FileHandleCommon.cpp new file mode 100644 index 000000000..0c465ec17 --- /dev/null +++ b/dom/filehandle/FileHandleCommon.cpp @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 "FileHandleCommon.h" + +#include "mozilla/Assertions.h" +#include "prthread.h" + +namespace mozilla { +namespace dom { + +#ifdef DEBUG + +void +ThreadObject::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mOwningThread); + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); +} + +PRThread* +ThreadObject::OwningThread() const +{ + MOZ_ASSERT(mOwningThread); + return mOwningThread; +} + +#endif // DEBUG + +} // namespace dom +} // namespace mozilla diff --git a/dom/filehandle/FileHandleCommon.h b/dom/filehandle/FileHandleCommon.h new file mode 100644 index 000000000..47cffa017 --- /dev/null +++ b/dom/filehandle/FileHandleCommon.h @@ -0,0 +1,73 @@ +/* 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_FileHandleCommon_h +#define mozilla_dom_FileHandleCommon_h + +#include "nscore.h" + +#ifdef DEBUG +#define DEBUGONLY(...) __VA_ARGS__ +#else +#define DEBUGONLY(...) /* nothing */ +#endif + +struct PRThread; + +namespace mozilla { +namespace dom { + +class RefCountedObject +{ +public: + NS_IMETHOD_(MozExternalRefCountType) + AddRef() = 0; + + NS_IMETHOD_(MozExternalRefCountType) + Release() = 0; + +protected: + virtual ~RefCountedObject() + { } +}; + +class ThreadObject +{ + DEBUGONLY(PRThread* mOwningThread;) + +public: + explicit ThreadObject(DEBUGONLY(PRThread* aOwningThread)) + DEBUGONLY(: mOwningThread(aOwningThread)) + { } + + virtual ~ThreadObject() + { } + +#ifdef DEBUG + void + AssertIsOnOwningThread() const; + + PRThread* + OwningThread() const; +#else + void + AssertIsOnOwningThread() const + { } +#endif +}; + +class RefCountedThreadObject + : public RefCountedObject + , public ThreadObject +{ +public: + explicit RefCountedThreadObject(DEBUGONLY(PRThread* aOwningThread)) + : ThreadObject(DEBUGONLY(aOwningThread)) + { } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FileHandleCommon_h diff --git a/dom/filehandle/FileHandleStorage.h b/dom/filehandle/FileHandleStorage.h new file mode 100644 index 000000000..957f4005a --- /dev/null +++ b/dom/filehandle/FileHandleStorage.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_FileHandleStorage_h +#define mozilla_dom_FileHandleStorage_h + +namespace mozilla { +namespace dom { + +enum FileHandleStorage +{ + FILE_HANDLE_STORAGE_IDB = 0, + // A placeholder for bug 997471 + //FILE_HANDLE_STORAGE_FS + FILE_HANDLE_STORAGE_MAX +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FileHandleStorage_h diff --git a/dom/filehandle/FileRequestBase.h b/dom/filehandle/FileRequestBase.h new file mode 100644 index 000000000..bff3997d7 --- /dev/null +++ b/dom/filehandle/FileRequestBase.h @@ -0,0 +1,93 @@ +/* -*- 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_FileRequest_h +#define mozilla_dom_FileRequest_h + +#include "FileHandleCommon.h" +#include "js/TypeDecls.h" +#include "nsString.h" + +struct PRThread; + +namespace mozilla { +namespace dom { + +class FileHandleBase; + +/** + * This class provides a base for FileRequest implementations. + */ +class FileRequestBase + : public RefCountedThreadObject +{ + nsString mEncoding; + + bool mHasEncoding; + +public: + class ResultCallback; + + void + SetEncoding(const nsAString& aEncoding) + { + mEncoding = aEncoding; + mHasEncoding = true; + } + + const nsAString& + GetEncoding() const + { + return mEncoding; + } + + bool + HasEncoding() const + { + return mHasEncoding; + } + + virtual FileHandleBase* + FileHandle() const = 0; + + virtual void + OnProgress(uint64_t aProgress, uint64_t aProgressMax) = 0; + + virtual void + SetResultCallback(ResultCallback* aCallback) = 0; + + virtual void + SetError(nsresult aError) = 0; + +protected: + FileRequestBase(DEBUGONLY(PRThread* aOwningThread)) + : RefCountedThreadObject(DEBUGONLY(aOwningThread)) + , mHasEncoding(false) + { + AssertIsOnOwningThread(); + } + + virtual ~FileRequestBase() + { + AssertIsOnOwningThread(); + } +}; + +class NS_NO_VTABLE FileRequestBase::ResultCallback +{ +public: + virtual nsresult + GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) = 0; + +protected: + ResultCallback() + { } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FileRequest_h diff --git a/dom/filehandle/MutableFileBase.cpp b/dom/filehandle/MutableFileBase.cpp new file mode 100644 index 000000000..dc577e8f5 --- /dev/null +++ b/dom/filehandle/MutableFileBase.cpp @@ -0,0 +1,36 @@ +/* -*- 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 "MutableFileBase.h" + +#include "ActorsChild.h" +#include "mozilla/Assertions.h" +#include "prthread.h" + +namespace mozilla { +namespace dom { + +MutableFileBase::MutableFileBase(DEBUGONLY(PRThread* aOwningThread,) + BackgroundMutableFileChildBase* aActor) + : RefCountedThreadObject(DEBUGONLY(aOwningThread)) + , mBackgroundActor(aActor) +{ + AssertIsOnOwningThread(); + MOZ_ASSERT(aActor); +} + +MutableFileBase::~MutableFileBase() +{ + AssertIsOnOwningThread(); + + if (mBackgroundActor) { + mBackgroundActor->SendDeleteMeInternal(); + MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!"); + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/filehandle/MutableFileBase.h b/dom/filehandle/MutableFileBase.h new file mode 100644 index 000000000..88d53ff78 --- /dev/null +++ b/dom/filehandle/MutableFileBase.h @@ -0,0 +1,76 @@ +/* -*- 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_MutableFile_h +#define mozilla_dom_MutableFile_h + +#include "mozilla/dom/FileHandleCommon.h" +#include "nscore.h" + +template <class> struct already_AddRefed; +class nsISupports; +class nsString; +struct PRThread; + +namespace mozilla { +namespace dom { + +class BackgroundMutableFileChildBase; +class BlobImpl; +class File; +class FileHandleBase; + +/** + * This class provides a base for MutableFile implementations. + * (for example IDBMutableFile provides IndexedDB specific implementation). + */ +class MutableFileBase + : public RefCountedThreadObject +{ +protected: + BackgroundMutableFileChildBase* mBackgroundActor; + +public: + BackgroundMutableFileChildBase* + GetBackgroundActor() const + { + AssertIsOnOwningThread(); + + return mBackgroundActor; + } + + void + ClearBackgroundActor() + { + AssertIsOnOwningThread(); + + mBackgroundActor = nullptr; + } + + virtual const nsString& + Name() const = 0; + + virtual const nsString& + Type() const = 0; + + virtual bool + IsInvalidated() = 0; + + virtual already_AddRefed<File> + CreateFileFor(BlobImpl* aBlobImpl, + FileHandleBase* aFileHandle) = 0; + +protected: + MutableFileBase(DEBUGONLY(PRThread* aOwningThread,) + BackgroundMutableFileChildBase* aActor); + + virtual ~MutableFileBase(); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_MutableFile_h diff --git a/dom/filehandle/PBackgroundFileHandle.ipdl b/dom/filehandle/PBackgroundFileHandle.ipdl new file mode 100644 index 000000000..60fdfafbf --- /dev/null +++ b/dom/filehandle/PBackgroundFileHandle.ipdl @@ -0,0 +1,91 @@ +/* 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 protocol PBlob; + +namespace mozilla { +namespace dom { + +struct FileRequestGetMetadataParams +{ + bool size; + bool lastModified; +}; + +struct FileRequestReadParams +{ + uint64_t offset; + uint64_t size; +}; + +struct FileRequestStringData +{ + nsCString string; +}; + +struct FileRequestBlobData +{ + PBlob blob; +}; + +union FileRequestData +{ + FileRequestStringData; + FileRequestBlobData; +}; + +struct FileRequestWriteParams +{ + uint64_t offset; + FileRequestData data; + uint64_t dataLength; +}; + +struct FileRequestTruncateParams +{ + uint64_t offset; +}; + +struct FileRequestFlushParams +{ +}; + +struct FileRequestGetFileParams +{ +}; + +union FileRequestParams +{ + FileRequestGetMetadataParams; + FileRequestReadParams; + FileRequestWriteParams; + FileRequestTruncateParams; + FileRequestFlushParams; + FileRequestGetFileParams; +}; + +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 000000000..9fb678b75 --- /dev/null +++ b/dom/filehandle/PBackgroundFileRequest.ipdl @@ -0,0 +1,83 @@ +/* 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 PBlob; + +using struct mozilla::void_t + from "ipc/IPCMessageUtils.h"; + +namespace mozilla { +namespace dom { + +union FileRequestSize +{ + void_t; + uint64_t; +}; + +union FileRequestLastModified +{ + void_t; + int64_t; +}; + +struct FileRequestMetadata +{ + FileRequestSize size; + FileRequestLastModified lastModified; +}; + +struct FileRequestGetMetadataResponse +{ + FileRequestMetadata metadata; +}; + +struct FileRequestReadResponse +{ + nsCString data; +}; + +struct FileRequestWriteResponse +{ +}; + +struct FileRequestTruncateResponse +{ +}; + +struct FileRequestFlushResponse +{ +}; + +struct FileRequestGetFileResponse +{ + PBlob file; + FileRequestMetadata metadata; +}; + +union FileRequestResponse +{ + nsresult; + FileRequestGetMetadataResponse; + FileRequestReadResponse; + FileRequestWriteResponse; + FileRequestTruncateResponse; + FileRequestFlushResponse; + FileRequestGetFileResponse; +}; + +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 000000000..0f87f3b6d --- /dev/null +++ b/dom/filehandle/PBackgroundMutableFile.ipdl @@ -0,0 +1,36 @@ +/* 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"; + +using mozilla::dom::FileMode + from "mozilla/dom/FileModeBinding.h"; + +namespace mozilla { +namespace dom { + +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 000000000..12a30442c --- /dev/null +++ b/dom/filehandle/SerializationHelpers.h @@ -0,0 +1,23 @@ +/* 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/IPCMessageUtils.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 000000000..b4e3b64c0 --- /dev/null +++ b/dom/filehandle/moz.build @@ -0,0 +1,45 @@ +# -*- 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/. + +EXPORTS.mozilla.dom.filehandle += [ + 'ActorsChild.h', + 'ActorsParent.h', + 'SerializationHelpers.h', +] + +EXPORTS.mozilla.dom += [ + 'FileHandleBase.h', + 'FileHandleCommon.h', + 'FileHandleStorage.h', + 'FileRequestBase.h', + 'MutableFileBase.h', +] + +UNIFIED_SOURCES += [ + 'ActorsChild.cpp', + 'ActorsParent.cpp', + 'FileHandleBase.cpp', + 'FileHandleCommon.cpp', + 'MutableFileBase.cpp', +] + +IPDL_SOURCES += [ + 'PBackgroundFileHandle.ipdl', + 'PBackgroundFileRequest.ipdl', + 'PBackgroundMutableFile.ipdl', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +LOCAL_INCLUDES += [ + '../base', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/xpcom/threads', +] |