зеркало из https://github.com/mozilla/gecko-dev.git
639 строки
14 KiB
C++
639 строки
14 KiB
C++
/* -*- 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/IPCBlobUtils.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 error;
|
|
uint64_t dataLength = aValue.GetSize(error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!dataLength) {
|
|
return nullptr;
|
|
}
|
|
|
|
PBackgroundChild* backgroundActor = BackgroundChild::GetForCurrentThread();
|
|
MOZ_ASSERT(backgroundActor);
|
|
|
|
IPCBlob ipcBlob;
|
|
nsresult rv =
|
|
IPCBlobUtils::Serialize(aValue.Impl(), backgroundActor, ipcBlob);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
FileRequestBlobData blobData;
|
|
blobData.blob() = ipcBlob;
|
|
|
|
// 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
|