diff options
Diffstat (limited to 'dom/filehandle/FileHandleBase.cpp')
-rw-r--r-- | dom/filehandle/FileHandleBase.cpp | 636 |
1 files changed, 636 insertions, 0 deletions
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 |