summaryrefslogtreecommitdiffstats
path: root/dom/filehandle
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/filehandle
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/filehandle')
-rw-r--r--dom/filehandle/ActorsChild.cpp743
-rw-r--r--dom/filehandle/ActorsChild.h174
-rw-r--r--dom/filehandle/ActorsParent.cpp2666
-rw-r--r--dom/filehandle/ActorsParent.h221
-rw-r--r--dom/filehandle/FileHandleBase.cpp636
-rw-r--r--dom/filehandle/FileHandleBase.h246
-rw-r--r--dom/filehandle/FileHandleCommon.cpp34
-rw-r--r--dom/filehandle/FileHandleCommon.h73
-rw-r--r--dom/filehandle/FileHandleStorage.h24
-rw-r--r--dom/filehandle/FileRequestBase.h93
-rw-r--r--dom/filehandle/MutableFileBase.cpp36
-rw-r--r--dom/filehandle/MutableFileBase.h76
-rw-r--r--dom/filehandle/PBackgroundFileHandle.ipdl91
-rw-r--r--dom/filehandle/PBackgroundFileRequest.ipdl83
-rw-r--r--dom/filehandle/PBackgroundMutableFile.ipdl36
-rw-r--r--dom/filehandle/SerializationHelpers.h23
-rw-r--r--dom/filehandle/moz.build45
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(&current)));
+ 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(&current));
+ MOZ_ASSERT(current);
+}
+
+nsIEventTarget*
+FileHandleThreadPool::GetThreadPoolEventTarget() const
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mThreadPool);
+
+ return mThreadPool;
+}
+
+#endif // DEBUG
+
+void
+FileHandleThreadPool::Enqueue(FileHandle* aFileHandle,
+ FileHandleOp* aFileHandleOp,
+ bool aFinish)
+{
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aFileHandle);
+ MOZ_ASSERT(!mShutdownRequested);
+
+ BackgroundMutableFileParentBase* mutableFile = aFileHandle->GetMutableFile();
+
+ const nsACString& directoryId = mutableFile->DirectoryId();
+ const nsAString& fileName = mutableFile->FileName();
+ bool modeIsWrite = aFileHandle->Mode() == FileMode::Readwrite;
+
+ DirectoryInfo* directoryInfo;
+ 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(&current)));
+ MOZ_ASSERT(current);
+}
+
+nsresult
+FileHandle::GetOrCreateStream(nsISupports** aStream)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!mStream) {
+ nsCOMPtr<nsISupports> stream =
+ mMutableFile->CreateStream(mMode == FileMode::Readonly);
+ if (NS_WARN_IF(!stream)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ stream.swap(mStream);
+ }
+
+ nsCOMPtr<nsISupports> stream(mStream);
+ stream.forget(aStream);
+
+ return NS_OK;
+}
+
+void
+FileHandle::Abort(bool aForce)
+{
+ AssertIsOnBackgroundThread();
+
+ mAborted = true;
+
+ if (aForce) {
+ mForceAborted = true;
+ }
+
+ MaybeFinishOrAbort();
+}
+
+void
+FileHandle::NoteActiveRequest()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
+
+ mActiveRequestCount++;
+}
+
+void
+FileHandle::NoteFinishedRequest()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mActiveRequestCount);
+
+ mActiveRequestCount--;
+
+ MaybeFinishOrAbort();
+}
+
+void
+FileHandle::Invalidate()
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
+
+ if (!mInvalidated) {
+ mInvalidated = true;
+ mInvalidatedOnAnyThread = true;
+
+ Abort(/* aForce */ true);
+ }
+}
+
+void
+FileHandle::SendCompleteNotification(bool aAborted)
+{
+ AssertIsOnBackgroundThread();
+
+ if (!IsActorDestroyed()) {
+ Unused << SendComplete(aAborted);
+ }
+}
+
+bool
+FileHandle::VerifyRequestParams(const FileRequestParams& aParams) const
+{
+ AssertIsOnBackgroundThread();
+ MOZ_ASSERT(aParams.type() != FileRequestParams::T__None);
+
+ switch (aParams.type()) {
+ case FileRequestParams::TFileRequestGetMetadataParams: {
+ const FileRequestGetMetadataParams& params =
+ aParams.get_FileRequestGetMetadataParams();
+
+ if (NS_WARN_IF(!params.size() && !params.lastModified())) {
+ 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',
+]