michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "LockedFile.h" michael@0: michael@0: #include "AsyncHelper.h" michael@0: #include "FileHandle.h" michael@0: #include "FileHelper.h" michael@0: #include "FileRequest.h" michael@0: #include "FileService.h" michael@0: #include "FileStreamWrappers.h" michael@0: #include "MemoryStreams.h" michael@0: #include "MetadataHelper.h" michael@0: #include "mozilla/dom/DOMRequest.h" michael@0: #include "mozilla/dom/EncodingUtils.h" michael@0: #include "mozilla/dom/LockedFileBinding.h" michael@0: #include "mozilla/dom/TypedArray.h" michael@0: #include "mozilla/dom/UnionTypes.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsIAppShell.h" michael@0: #include "nsIDOMEvent.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsIFileStorage.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsWidgetsCID.h" michael@0: michael@0: #define STREAM_COPY_BLOCK_SIZE 32768 michael@0: michael@0: BEGIN_FILE_NAMESPACE michael@0: michael@0: namespace { michael@0: michael@0: NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: michael@0: class ReadHelper : public FileHelper michael@0: { michael@0: public: michael@0: ReadHelper(LockedFile* aLockedFile, michael@0: FileRequest* aFileRequest, michael@0: uint64_t aLocation, michael@0: uint64_t aSize) michael@0: : FileHelper(aLockedFile, aFileRequest), michael@0: mLocation(aLocation), mSize(aSize) michael@0: { michael@0: NS_ASSERTION(mSize, "Passed zero size!"); michael@0: } michael@0: michael@0: nsresult michael@0: Init(); michael@0: michael@0: nsresult michael@0: DoAsyncRun(nsISupports* aStream) MOZ_OVERRIDE; michael@0: michael@0: nsresult michael@0: GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: uint64_t mLocation; michael@0: uint64_t mSize; michael@0: michael@0: nsRefPtr mStream; michael@0: }; michael@0: michael@0: class ReadTextHelper : public ReadHelper michael@0: { michael@0: public: michael@0: ReadTextHelper(LockedFile* aLockedFile, michael@0: FileRequest* aFileRequest, michael@0: uint64_t aLocation, michael@0: uint64_t aSize, michael@0: const nsAString& aEncoding) michael@0: : ReadHelper(aLockedFile, aFileRequest, aLocation, aSize), michael@0: mEncoding(aEncoding) michael@0: { } michael@0: michael@0: nsresult michael@0: GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: nsString mEncoding; michael@0: }; michael@0: michael@0: class WriteHelper : public FileHelper michael@0: { michael@0: public: michael@0: WriteHelper(LockedFile* aLockedFile, michael@0: FileRequest* aFileRequest, michael@0: uint64_t aLocation, michael@0: nsIInputStream* aStream, michael@0: uint64_t aLength) michael@0: : FileHelper(aLockedFile, aFileRequest), michael@0: mLocation(aLocation), mStream(aStream), mLength(aLength) michael@0: { michael@0: NS_ASSERTION(mLength, "Passed zero length!"); michael@0: } michael@0: michael@0: nsresult michael@0: DoAsyncRun(nsISupports* aStream); michael@0: michael@0: private: michael@0: uint64_t mLocation; michael@0: nsCOMPtr mStream; michael@0: uint64_t mLength; michael@0: }; michael@0: michael@0: class TruncateHelper : public FileHelper michael@0: { michael@0: public: michael@0: TruncateHelper(LockedFile* aLockedFile, michael@0: FileRequest* aFileRequest, michael@0: uint64_t aOffset) michael@0: : FileHelper(aLockedFile, aFileRequest), michael@0: mOffset(aOffset) michael@0: { } michael@0: michael@0: nsresult michael@0: DoAsyncRun(nsISupports* aStream); michael@0: michael@0: private: michael@0: class AsyncTruncator : public AsyncHelper michael@0: { michael@0: public: michael@0: AsyncTruncator(nsISupports* aStream, int64_t aOffset) michael@0: : AsyncHelper(aStream), michael@0: mOffset(aOffset) michael@0: { } michael@0: protected: michael@0: nsresult michael@0: DoStreamWork(nsISupports* aStream); michael@0: michael@0: uint64_t mOffset; michael@0: }; michael@0: michael@0: uint64_t mOffset; michael@0: }; michael@0: michael@0: class FlushHelper : public FileHelper michael@0: { michael@0: public: michael@0: FlushHelper(LockedFile* aLockedFile, michael@0: FileRequest* aFileRequest) michael@0: : FileHelper(aLockedFile, aFileRequest) michael@0: { } michael@0: michael@0: nsresult michael@0: DoAsyncRun(nsISupports* aStream); michael@0: michael@0: private: michael@0: class AsyncFlusher : public AsyncHelper michael@0: { michael@0: public: michael@0: AsyncFlusher(nsISupports* aStream) michael@0: : AsyncHelper(aStream) michael@0: { } michael@0: protected: michael@0: nsresult michael@0: DoStreamWork(nsISupports* aStream); michael@0: }; michael@0: }; michael@0: michael@0: class OpenStreamHelper : public FileHelper michael@0: { michael@0: public: michael@0: OpenStreamHelper(LockedFile* aLockedFile, michael@0: bool aWholeFile, michael@0: uint64_t aStart, michael@0: uint64_t aLength) michael@0: : FileHelper(aLockedFile, nullptr), michael@0: mWholeFile(aWholeFile), mStart(aStart), mLength(aLength) michael@0: { } michael@0: michael@0: nsresult michael@0: DoAsyncRun(nsISupports* aStream); michael@0: michael@0: nsCOMPtr& michael@0: Result() michael@0: { michael@0: return mStream; michael@0: } michael@0: michael@0: private: michael@0: bool mWholeFile; michael@0: uint64_t mStart; michael@0: uint64_t mLength; michael@0: michael@0: nsCOMPtr mStream; michael@0: }; michael@0: michael@0: already_AddRefed michael@0: CreateGenericEvent(mozilla::dom::EventTarget* aEventOwner, michael@0: const nsAString& aType, bool aBubbles, bool aCancelable) michael@0: { michael@0: nsCOMPtr event; michael@0: NS_NewDOMEvent(getter_AddRefs(event), aEventOwner, nullptr, nullptr); michael@0: nsresult rv = event->InitEvent(aType, aBubbles, aCancelable); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: return event.forget(); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // static michael@0: already_AddRefed michael@0: LockedFile::Create(FileHandle* aFileHandle, michael@0: FileMode aMode, michael@0: RequestMode aRequestMode) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr lockedFile = new LockedFile(); michael@0: michael@0: lockedFile->BindToOwner(aFileHandle); michael@0: michael@0: lockedFile->mFileHandle = aFileHandle; michael@0: lockedFile->mMode = aMode; michael@0: lockedFile->mRequestMode = aRequestMode; michael@0: michael@0: nsCOMPtr appShell = do_GetService(kAppShellCID); michael@0: NS_ENSURE_TRUE(appShell, nullptr); michael@0: michael@0: nsresult rv = appShell->RunBeforeNextEvent(lockedFile); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: lockedFile->mCreating = true; michael@0: michael@0: FileService* service = FileService::GetOrCreate(); michael@0: NS_ENSURE_TRUE(service, nullptr); michael@0: michael@0: rv = service->Enqueue(lockedFile, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: return lockedFile.forget(); michael@0: } michael@0: michael@0: LockedFile::LockedFile() michael@0: : mReadyState(INITIAL), michael@0: mMode(FileMode::Readonly), michael@0: mRequestMode(NORMAL), michael@0: mLocation(0), michael@0: mPendingRequests(0), michael@0: mAborted(false), michael@0: mCreating(false) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: LockedFile::~LockedFile() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(LockedFile, DOMEventTargetHelper, michael@0: mFileHandle) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(LockedFile) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRunnable) michael@0: NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(LockedFile, DOMEventTargetHelper) michael@0: NS_IMPL_RELEASE_INHERITED(LockedFile, DOMEventTargetHelper) michael@0: michael@0: nsresult michael@0: LockedFile::PreHandleEvent(EventChainPreVisitor& aVisitor) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: aVisitor.mCanHandle = true; michael@0: aVisitor.mParentTarget = mFileHandle; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: LockedFile::OnNewRequest() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: if (!mPendingRequests) { michael@0: NS_ASSERTION(mReadyState == INITIAL, michael@0: "Reusing a locked file!"); michael@0: mReadyState = LOADING; michael@0: } michael@0: ++mPendingRequests; michael@0: } michael@0: michael@0: void michael@0: LockedFile::OnRequestFinished() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mPendingRequests, "Mismatched calls!"); michael@0: --mPendingRequests; michael@0: if (!mPendingRequests) { michael@0: NS_ASSERTION(mAborted || mReadyState == LOADING, michael@0: "Bad state!"); michael@0: mReadyState = LockedFile::FINISHING; michael@0: Finish(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: LockedFile::CreateParallelStream(nsISupports** aStream) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsIFileStorage* fileStorage = mFileHandle->mFileStorage; michael@0: if (fileStorage->IsInvalidated()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: nsCOMPtr stream = michael@0: mFileHandle->CreateStream(mFileHandle->mFile, mMode == FileMode::Readonly); michael@0: NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); michael@0: michael@0: mParallelStreams.AppendElement(stream); michael@0: michael@0: stream.forget(aStream); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: LockedFile::GetOrCreateStream(nsISupports** aStream) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsIFileStorage* fileStorage = mFileHandle->mFileStorage; michael@0: if (fileStorage->IsInvalidated()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: if (!mStream) { michael@0: nsCOMPtr stream = michael@0: mFileHandle->CreateStream(mFileHandle->mFile, mMode == FileMode::Readonly); michael@0: NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); michael@0: michael@0: stream.swap(mStream); michael@0: } michael@0: michael@0: nsCOMPtr stream(mStream); michael@0: stream.forget(aStream); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::GenerateFileRequest() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: return FileRequest::Create(GetOwner(), this, /* aWrapAsDOMRequest */ false); michael@0: } michael@0: michael@0: bool michael@0: LockedFile::IsOpen() const michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // If we haven't started anything then we're open. michael@0: if (mReadyState == INITIAL) { michael@0: NS_ASSERTION(FileHelper::GetCurrentLockedFile() != this, michael@0: "This should be some other locked file (or null)!"); michael@0: return true; michael@0: } michael@0: michael@0: // If we've already started then we need to check to see if we still have the michael@0: // mCreating flag set. If we do (i.e. we haven't returned to the event loop michael@0: // from the time we were created) then we are open. Otherwise check the michael@0: // currently running locked files to see if it's the same. We only allow other michael@0: // requests to be made if this locked file is currently running. michael@0: if (mReadyState == LOADING) { michael@0: if (mCreating) { michael@0: return true; michael@0: } michael@0: michael@0: if (FileHelper::GetCurrentLockedFile() == this) { michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // virtual michael@0: JSObject* michael@0: LockedFile::WrapObject(JSContext* aCx) michael@0: { michael@0: return LockedFileBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::GetMetadata(const DOMFileMetadataParameters& aParameters, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // Common state checking michael@0: if (!CheckState(aRv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Do nothing if the window is closed michael@0: if (!GetOwner()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr params = michael@0: new MetadataParameters(aParameters.mSize, aParameters.mLastModified); michael@0: if (!params->IsConfigured()) { michael@0: aRv.ThrowTypeError(MSG_METADATA_NOT_CONFIGURED); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr fileRequest = GenerateFileRequest(); michael@0: michael@0: nsRefPtr helper = michael@0: new MetadataHelper(this, fileRequest, params); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return fileRequest.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::ReadAsArrayBuffer(uint64_t aSize, ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // State and argument checking for read michael@0: if (!CheckStateAndArgumentsForRead(aSize, aRv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Do nothing if the window is closed michael@0: if (!GetOwner()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr fileRequest = GenerateFileRequest(); michael@0: michael@0: nsRefPtr helper = michael@0: new ReadHelper(this, fileRequest, mLocation, aSize); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(helper->Init())) || michael@0: NS_WARN_IF(NS_FAILED(helper->Enqueue()))) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: mLocation += aSize; michael@0: michael@0: return fileRequest.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::ReadAsText(uint64_t aSize, const nsAString& aEncoding, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // State and argument checking for read michael@0: if (!CheckStateAndArgumentsForRead(aSize, aRv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Do nothing if the window is closed michael@0: if (!GetOwner()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr fileRequest = GenerateFileRequest(); michael@0: michael@0: nsRefPtr helper = michael@0: new ReadTextHelper(this, fileRequest, mLocation, aSize, aEncoding); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(helper->Init())) || michael@0: NS_WARN_IF(NS_FAILED(helper->Enqueue()))) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: mLocation += aSize; michael@0: michael@0: return fileRequest.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::Truncate(const Optional& aSize, ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // State checking for write michael@0: if (!CheckStateForWrite(aRv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Getting location and additional state checking for truncate michael@0: uint64_t location; michael@0: if (aSize.WasPassed()) { michael@0: // Just in case someone calls us from C++ michael@0: NS_ASSERTION(aSize.Value() != UINT64_MAX, "Passed wrong size!"); michael@0: location = aSize.Value(); michael@0: } else { michael@0: if (mLocation == UINT64_MAX) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); michael@0: return nullptr; michael@0: } michael@0: location = mLocation; michael@0: } michael@0: michael@0: // Do nothing if the window is closed michael@0: if (!GetOwner()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr fileRequest = GenerateFileRequest(); michael@0: michael@0: nsRefPtr helper = michael@0: new TruncateHelper(this, fileRequest, location); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aSize.WasPassed()) { michael@0: mLocation = aSize.Value(); michael@0: } michael@0: michael@0: return fileRequest.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::Flush(ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // State checking for write michael@0: if (!CheckStateForWrite(aRv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // Do nothing if the window is closed michael@0: if (!GetOwner()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr fileRequest = GenerateFileRequest(); michael@0: michael@0: nsRefPtr helper = new FlushHelper(this, fileRequest); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: return fileRequest.forget(); michael@0: } michael@0: michael@0: void michael@0: LockedFile::Abort(ErrorResult& aRv) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // This method is special enough for not using generic state checking methods. michael@0: michael@0: // We can't use IsOpen here since we need it to be possible to call Abort() michael@0: // even from outside of transaction callbacks. michael@0: if (mReadyState != LockedFile::INITIAL && michael@0: mReadyState != LockedFile::LOADING) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); michael@0: return; michael@0: } michael@0: michael@0: bool needToFinish = mReadyState == INITIAL; michael@0: michael@0: mAborted = true; michael@0: mReadyState = DONE; michael@0: michael@0: // Fire the abort event if there are no outstanding requests. Otherwise the michael@0: // abort event will be fired when all outstanding requests finish. michael@0: if (needToFinish) { michael@0: aRv = Finish(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: LockedFile::Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: // We're back at the event loop, no longer newborn. michael@0: mCreating = false; michael@0: michael@0: // Maybe set the readyState to DONE if there were no requests generated. michael@0: if (mReadyState == INITIAL) { michael@0: mReadyState = DONE; michael@0: michael@0: if (NS_FAILED(Finish())) { michael@0: NS_WARNING("Failed to finish!"); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: LockedFile::OpenInputStream(bool aWholeFile, uint64_t aStart, uint64_t aLength, michael@0: nsIInputStream** aResult) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mRequestMode == PARALLEL, michael@0: "Don't call me in other than parallel mode!"); michael@0: michael@0: // Common state checking michael@0: ErrorResult error; michael@0: if (!CheckState(error)) { michael@0: return error.ErrorCode(); michael@0: } michael@0: michael@0: // Do nothing if the window is closed michael@0: if (!GetOwner()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr helper = michael@0: new OpenStreamHelper(this, aWholeFile, aStart, aLength); michael@0: michael@0: nsresult rv = helper->Enqueue(); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: michael@0: nsCOMPtr& result = helper->Result(); michael@0: NS_ENSURE_TRUE(result, NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: michael@0: result.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: LockedFile::CheckState(ErrorResult& aRv) michael@0: { michael@0: if (!IsOpen()) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_LOCKEDFILE_INACTIVE_ERR); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: LockedFile::CheckStateAndArgumentsForRead(uint64_t aSize, ErrorResult& aRv) michael@0: { michael@0: // Common state checking michael@0: if (!CheckState(aRv)) { michael@0: return false; michael@0: } michael@0: michael@0: // Additional state checking for read michael@0: if (mLocation == UINT64_MAX) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR); michael@0: return false; michael@0: } michael@0: michael@0: // Argument checking for read michael@0: if (!aSize) { michael@0: aRv.ThrowTypeError(MSG_INVALID_READ_SIZE); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: LockedFile::CheckStateForWrite(ErrorResult& aRv) michael@0: { michael@0: // Common state checking michael@0: if (!CheckState(aRv)) { michael@0: return false; michael@0: } michael@0: michael@0: // Additional state checking for write michael@0: if (mMode != FileMode::Readwrite) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: already_AddRefed michael@0: LockedFile::WriteInternal(nsIInputStream* aInputStream, uint64_t aInputLength, michael@0: bool aAppend, ErrorResult& aRv) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: DebugOnly error; michael@0: MOZ_ASSERT(CheckStateForWrite(error)); michael@0: MOZ_ASSERT_IF(!aAppend, mLocation != UINT64_MAX); michael@0: MOZ_ASSERT(aInputStream); michael@0: MOZ_ASSERT(aInputLength); michael@0: MOZ_ASSERT(GetOwner()); michael@0: michael@0: nsRefPtr fileRequest = GenerateFileRequest(); michael@0: michael@0: uint64_t location = aAppend ? UINT64_MAX : mLocation; michael@0: michael@0: nsRefPtr helper = michael@0: new WriteHelper(this, fileRequest, location, aInputStream, aInputLength); michael@0: michael@0: if (NS_WARN_IF(NS_FAILED(helper->Enqueue()))) { michael@0: aRv.Throw(NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (aAppend) { michael@0: mLocation = UINT64_MAX; michael@0: } michael@0: else { michael@0: mLocation += aInputLength; michael@0: } michael@0: michael@0: return fileRequest.forget(); michael@0: } michael@0: michael@0: nsresult michael@0: LockedFile::Finish() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsRefPtr helper(new FinishHelper(this)); michael@0: michael@0: FileService* service = FileService::Get(); michael@0: NS_ASSERTION(service, "This should never be null"); michael@0: michael@0: nsIEventTarget* target = service->StreamTransportTarget(); michael@0: michael@0: nsresult rv = target->Dispatch(helper, NS_DISPATCH_NORMAL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: LockedFile::GetInputStream(const ArrayBuffer& aValue, uint64_t* aInputLength, michael@0: ErrorResult& aRv) michael@0: { michael@0: aValue.ComputeLengthAndData(); michael@0: michael@0: const char* data = reinterpret_cast(aValue.Data()); michael@0: uint32_t length = aValue.Length(); michael@0: michael@0: nsCOMPtr stream; michael@0: aRv = NS_NewByteInputStream(getter_AddRefs(stream), data, length, michael@0: NS_ASSIGNMENT_COPY); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: *aInputLength = length; michael@0: return stream.forget(); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: LockedFile::GetInputStream(nsIDOMBlob* aValue, uint64_t* aInputLength, michael@0: ErrorResult& aRv) michael@0: { michael@0: uint64_t length; michael@0: aRv = aValue->GetSize(&length); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr stream; michael@0: aRv = aValue->GetInternalStream(getter_AddRefs(stream)); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: *aInputLength = length; michael@0: return stream.forget(); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: LockedFile::GetInputStream(const nsAString& aValue, uint64_t* aInputLength, michael@0: ErrorResult& aRv) michael@0: { michael@0: NS_ConvertUTF16toUTF8 cstr(aValue); michael@0: michael@0: nsCOMPtr stream; michael@0: aRv = NS_NewCStringInputStream(getter_AddRefs(stream), cstr); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: *aInputLength = cstr.Length(); michael@0: return stream.forget(); michael@0: } michael@0: michael@0: FinishHelper::FinishHelper(LockedFile* aLockedFile) michael@0: : mLockedFile(aLockedFile), michael@0: mAborted(aLockedFile->mAborted) michael@0: { michael@0: mParallelStreams.SwapElements(aLockedFile->mParallelStreams); michael@0: mStream.swap(aLockedFile->mStream); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(FinishHelper, nsIRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: FinishHelper::Run() michael@0: { michael@0: if (NS_IsMainThread()) { michael@0: mLockedFile->mReadyState = LockedFile::DONE; michael@0: michael@0: FileService* service = FileService::Get(); michael@0: if (service) { michael@0: service->NotifyLockedFileCompleted(mLockedFile); michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: if (mAborted) { michael@0: event = CreateGenericEvent(mLockedFile, NS_LITERAL_STRING("abort"), michael@0: true, false); michael@0: } michael@0: else { michael@0: event = CreateGenericEvent(mLockedFile, NS_LITERAL_STRING("complete"), michael@0: false, false); michael@0: } michael@0: NS_ENSURE_TRUE(event, NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR); michael@0: michael@0: bool dummy; michael@0: if (NS_FAILED(mLockedFile->DispatchEvent(event, &dummy))) { michael@0: NS_WARNING("Dispatch failed!"); michael@0: } michael@0: michael@0: mLockedFile = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFileStorage* fileStorage = mLockedFile->mFileHandle->mFileStorage; michael@0: if (fileStorage->IsInvalidated()) { michael@0: mAborted = true; michael@0: } michael@0: michael@0: for (uint32_t index = 0; index < mParallelStreams.Length(); index++) { michael@0: nsCOMPtr stream = michael@0: do_QueryInterface(mParallelStreams[index]); michael@0: michael@0: if (NS_FAILED(stream->Close())) { michael@0: NS_WARNING("Failed to close stream!"); michael@0: } michael@0: michael@0: mParallelStreams[index] = nullptr; michael@0: } michael@0: michael@0: if (mStream) { michael@0: nsCOMPtr stream = do_QueryInterface(mStream); michael@0: michael@0: if (NS_FAILED(stream->Close())) { michael@0: NS_WARNING("Failed to close stream!"); michael@0: } michael@0: michael@0: mStream = nullptr; michael@0: } michael@0: michael@0: return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: ReadHelper::Init() michael@0: { michael@0: mStream = MemoryOutputStream::Create(mSize); michael@0: NS_ENSURE_TRUE(mStream, NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ReadHelper::DoAsyncRun(nsISupports* aStream) michael@0: { michael@0: NS_ASSERTION(aStream, "Passed a null stream!"); michael@0: michael@0: uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS; michael@0: michael@0: nsCOMPtr istream = michael@0: new FileInputStreamWrapper(aStream, this, mLocation, mSize, flags); michael@0: michael@0: FileService* service = FileService::Get(); michael@0: NS_ASSERTION(service, "This should never be null"); michael@0: michael@0: nsIEventTarget* target = service->StreamTransportTarget(); michael@0: michael@0: nsCOMPtr copier; michael@0: nsresult rv = michael@0: NS_NewAsyncStreamCopier(getter_AddRefs(copier), istream, mStream, target, michael@0: false, true, STREAM_COPY_BLOCK_SIZE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = copier->AsyncCopy(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mRequest = do_QueryInterface(copier); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ReadHelper::GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) michael@0: { michael@0: JS::Rooted arrayBuffer(aCx); michael@0: nsresult rv = michael@0: nsContentUtils::CreateArrayBuffer(aCx, mStream->Data(), arrayBuffer.address()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aVal.setObject(*arrayBuffer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ReadTextHelper::GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) michael@0: { michael@0: nsAutoCString encoding; michael@0: const nsCString& data = mStream->Data(); michael@0: // The BOM sniffing is baked into the "decode" part of the Encoding michael@0: // Standard, which the File API references. michael@0: if (!nsContentUtils::CheckForBOM( michael@0: reinterpret_cast(data.get()), michael@0: data.Length(), michael@0: encoding)) { michael@0: // BOM sniffing failed. Try the API argument. michael@0: if (!EncodingUtils::FindEncodingForLabel(mEncoding, encoding)) { michael@0: // API argument failed. Since we are dealing with a file system file, michael@0: // we don't have a meaningful type attribute for the blob available, michael@0: // so proceeding to the next step, which is defaulting to UTF-8. michael@0: encoding.AssignLiteral("UTF-8"); michael@0: } michael@0: } michael@0: michael@0: nsString tmpString; michael@0: nsresult rv = nsContentUtils::ConvertStringFromEncoding(encoding, data, michael@0: tmpString); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!xpc::StringToJsval(aCx, tmpString, aVal)) { michael@0: NS_WARNING("Failed to convert string!"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: WriteHelper::DoAsyncRun(nsISupports* aStream) michael@0: { michael@0: NS_ASSERTION(aStream, "Passed a null stream!"); michael@0: michael@0: uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS; michael@0: michael@0: nsCOMPtr ostream = michael@0: new FileOutputStreamWrapper(aStream, this, mLocation, mLength, flags); michael@0: michael@0: FileService* service = FileService::Get(); michael@0: NS_ASSERTION(service, "This should never be null"); michael@0: michael@0: nsIEventTarget* target = service->StreamTransportTarget(); michael@0: michael@0: nsCOMPtr copier; michael@0: nsresult rv = michael@0: NS_NewAsyncStreamCopier(getter_AddRefs(copier), mStream, ostream, target, michael@0: true, false, STREAM_COPY_BLOCK_SIZE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = copier->AsyncCopy(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mRequest = do_QueryInterface(copier); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TruncateHelper::DoAsyncRun(nsISupports* aStream) michael@0: { michael@0: NS_ASSERTION(aStream, "Passed a null stream!"); michael@0: michael@0: nsRefPtr truncator = new AsyncTruncator(aStream, mOffset); michael@0: michael@0: nsresult rv = truncator->AsyncWork(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: TruncateHelper::AsyncTruncator::DoStreamWork(nsISupports* aStream) michael@0: { michael@0: nsCOMPtr sstream = do_QueryInterface(aStream); michael@0: michael@0: nsresult rv = sstream->Seek(nsISeekableStream::NS_SEEK_SET, mOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sstream->SetEOF(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: FlushHelper::DoAsyncRun(nsISupports* aStream) michael@0: { michael@0: NS_ASSERTION(aStream, "Passed a null stream!"); michael@0: michael@0: nsRefPtr flusher = new AsyncFlusher(aStream); michael@0: michael@0: nsresult rv = flusher->AsyncWork(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: FlushHelper::AsyncFlusher::DoStreamWork(nsISupports* aStream) michael@0: { michael@0: nsCOMPtr ostream = do_QueryInterface(aStream); michael@0: michael@0: nsresult rv = ostream->Flush(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OpenStreamHelper::DoAsyncRun(nsISupports* aStream) michael@0: { michael@0: NS_ASSERTION(aStream, "Passed a null stream!"); michael@0: michael@0: uint32_t flags = FileStreamWrapper::NOTIFY_CLOSE | michael@0: FileStreamWrapper::NOTIFY_DESTROY; michael@0: michael@0: mStream = mWholeFile ? michael@0: new FileInputStreamWrapper(aStream, this, 0, mLength, flags) : michael@0: new FileInputStreamWrapper(aStream, this, mStart, mLength, flags); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: END_FILE_NAMESPACE