diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/file/FileReader.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/file/FileReader.cpp')
-rw-r--r-- | dom/file/FileReader.cpp | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/dom/file/FileReader.cpp b/dom/file/FileReader.cpp new file mode 100644 index 0000000000..93948671df --- /dev/null +++ b/dom/file/FileReader.cpp @@ -0,0 +1,803 @@ +/* -*- 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 "FileReader.h" + +#include "nsIGlobalObject.h" +#include "nsITimer.h" + +#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents +#include "mozilla/Base64.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FileReaderBinding.h" +#include "mozilla/dom/ProgressEvent.h" +#include "mozilla/dom/UnionTypes.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/WorkerScope.h" +#include "mozilla/Encoding.h" +#include "mozilla/HoldDropJSObjects.h" +#include "nsAlgorithm.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDOMJSUtils.h" +#include "nsError.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" +#include "nsReadableUtils.h" + +namespace mozilla::dom { + +#define ABORT_STR u"abort" +#define LOAD_STR u"load" +#define LOADSTART_STR u"loadstart" +#define LOADEND_STR u"loadend" +#define ERROR_STR u"error" +#define PROGRESS_STR u"progress" + +const uint64_t kUnknownSize = uint64_t(-1); + +NS_IMPL_CYCLE_COLLECTION_CLASS(FileReader) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileReader, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader, + DOMEventTargetHelper) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mError) + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FileReader, DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileReader) + NS_INTERFACE_MAP_ENTRY_CONCRETE(FileReader) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsINamed) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper) + +class MOZ_RAII FileReaderDecreaseBusyCounter { + RefPtr<FileReader> mFileReader; + + public: + explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader) + : mFileReader(aFileReader) {} + + ~FileReaderDecreaseBusyCounter() { mFileReader->DecreaseBusyCounter(); } +}; + +class FileReader::AsyncWaitRunnable final : public CancelableRunnable { + public: + explicit AsyncWaitRunnable(FileReader* aReader) + : CancelableRunnable("FileReader::AsyncWaitRunnable"), mReader(aReader) {} + + NS_IMETHOD + Run() override { + if (mReader) { + mReader->InitialAsyncWait(); + } + return NS_OK; + } + + nsresult Cancel() override { + mReader = nullptr; + return NS_OK; + } + + public: + RefPtr<FileReader> mReader; +}; + +void FileReader::RootResultArrayBuffer() { mozilla::HoldJSObjects(this); } + +// FileReader constructors/initializers + +FileReader::FileReader(nsIGlobalObject* aGlobal, WeakWorkerRef* aWorkerRef) + : DOMEventTargetHelper(aGlobal), + mFileData(nullptr), + mDataLen(0), + mDataFormat(FILE_AS_BINARY), + mResultArrayBuffer(nullptr), + mProgressEventWasDelayed(false), + mTimerIsActive(false), + mReadyState(EMPTY), + mTotal(0), + mTransferred(0), + mBusyCount(0), + mWeakWorkerRef(aWorkerRef) { + MOZ_ASSERT(aGlobal); + MOZ_ASSERT_IF(NS_IsMainThread(), !mWeakWorkerRef); + + if (NS_IsMainThread()) { + mTarget = aGlobal->EventTargetFor(TaskCategory::Other); + } else { + mTarget = GetCurrentSerialEventTarget(); + } + + SetDOMStringToNull(mResult); +} + +FileReader::~FileReader() { + Shutdown(); + DropJSObjects(this); +} + +/* static */ +already_AddRefed<FileReader> FileReader::Constructor( + const GlobalObject& aGlobal) { + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<WeakWorkerRef> workerRef; + + if (!NS_IsMainThread()) { + JSContext* cx = aGlobal.Context(); + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); + + workerRef = WeakWorkerRef::Create(workerPrivate); + } + + RefPtr<FileReader> fileReader = new FileReader(global, workerRef); + + return fileReader.forget(); +} + +// nsIInterfaceRequestor + +NS_IMETHODIMP +FileReader::GetInterface(const nsIID& aIID, void** aResult) { + return QueryInterface(aIID, aResult); +} + +void FileReader::GetResult(JSContext* aCx, + Nullable<OwningStringOrArrayBuffer>& aResult) { + JS::Rooted<JS::Value> result(aCx); + + if (mDataFormat == FILE_AS_ARRAYBUFFER) { + if (mReadyState != DONE || !mResultArrayBuffer || + !aResult.SetValue().SetAsArrayBuffer().Init(mResultArrayBuffer)) { + aResult.SetNull(); + } + + return; + } + + if (mReadyState != DONE || mResult.IsVoid()) { + aResult.SetNull(); + return; + } + + aResult.SetValue().SetAsString() = mResult; +} + +void FileReader::OnLoadEndArrayBuffer() { + AutoJSAPI jsapi; + if (!jsapi.Init(GetParentObject())) { + FreeDataAndDispatchError(NS_ERROR_FAILURE); + return; + } + + RootResultArrayBuffer(); + + JSContext* cx = jsapi.cx(); + + mResultArrayBuffer = JS::NewArrayBufferWithContents(cx, mDataLen, mFileData); + if (mResultArrayBuffer) { + mFileData = nullptr; // Transfer ownership + FreeDataAndDispatchSuccess(); + return; + } + + // Let's handle the error status. + + JS::Rooted<JS::Value> exceptionValue(cx); + if (!JS_GetPendingException(cx, &exceptionValue) || + // This should not really happen, exception should always be an object. + !exceptionValue.isObject()) { + JS_ClearPendingException(jsapi.cx()); + FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY); + return; + } + + JS_ClearPendingException(jsapi.cx()); + + JS::Rooted<JSObject*> exceptionObject(cx, &exceptionValue.toObject()); + JSErrorReport* er = JS_ErrorFromException(cx, exceptionObject); + if (!er || er->message()) { + FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY); + return; + } + + nsAutoString errorName; + JSLinearString* name = js::GetErrorTypeName(cx, er->exnType); + if (name) { + AssignJSLinearString(errorName, name); + } + + nsAutoCString errorMsg(er->message().c_str()); + nsAutoCString errorNameC = NS_LossyConvertUTF16toASCII(errorName); + // XXX Code selected arbitrarily + mError = + new DOMException(NS_ERROR_DOM_INVALID_STATE_ERR, errorMsg, errorNameC, + DOMException_Binding::INVALID_STATE_ERR); + + FreeDataAndDispatchError(); +} + +nsresult FileReader::DoAsyncWait() { + nsresult rv = IncreaseBusyCounter(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mAsyncStream->AsyncWait(this, + /* aFlags*/ 0, + /* aRequestedCount */ 0, mTarget); + if (NS_WARN_IF(NS_FAILED(rv))) { + DecreaseBusyCounter(); + return rv; + } + + return NS_OK; +} + +namespace { + +void PopulateBufferForBinaryString(char16_t* aDest, const char* aSource, + uint32_t aCount) { + // Zero-extend each char to char16_t. + ConvertLatin1toUtf16(Span(aSource, aCount), Span(aDest, aCount)); +} + +nsresult ReadFuncBinaryString(nsIInputStream* aInputStream, void* aClosure, + const char* aFromRawSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCount) { + char16_t* dest = static_cast<char16_t*>(aClosure) + aToOffset; + PopulateBufferForBinaryString(dest, aFromRawSegment, aCount); + *aWriteCount = aCount; + return NS_OK; +} + +} // namespace + +nsresult FileReader::DoReadData(uint64_t aCount) { + MOZ_ASSERT(mAsyncStream); + + uint32_t bytesRead = 0; + + if (mDataFormat == FILE_AS_BINARY) { + // Continuously update our binary string as data comes in + CheckedInt<uint64_t> size{mResult.Length()}; + size += aCount; + + if (!size.isValid() || size.value() > UINT32_MAX || size.value() > mTotal) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t lenBeforeRead = mResult.Length(); + MOZ_ASSERT(lenBeforeRead == mDataLen, "unexpected mResult length"); + + mResult.SetLength(lenBeforeRead + aCount); + char16_t* currentPos = mResult.BeginWriting() + lenBeforeRead; + + if (NS_InputStreamIsBuffered(mAsyncStream)) { + nsresult rv = mAsyncStream->ReadSegments(ReadFuncBinaryString, currentPos, + aCount, &bytesRead); + NS_ENSURE_SUCCESS(rv, NS_OK); + } else { + while (aCount > 0) { + char tmpBuffer[4096]; + uint32_t minCount = + XPCOM_MIN(aCount, static_cast<uint64_t>(sizeof(tmpBuffer))); + uint32_t read = 0; + + nsresult rv = mAsyncStream->Read(tmpBuffer, minCount, &read); + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + } + + NS_ENSURE_SUCCESS(rv, NS_OK); + + if (read == 0) { + // The stream finished too early. + return NS_ERROR_OUT_OF_MEMORY; + } + + PopulateBufferForBinaryString(currentPos, tmpBuffer, read); + + currentPos += read; + aCount -= read; + bytesRead += read; + } + } + + MOZ_ASSERT(size.value() == lenBeforeRead + bytesRead); + mResult.Truncate(size.value()); + } else { + CheckedInt<uint64_t> size = mDataLen; + size += aCount; + + // Update memory buffer to reflect the contents of the file + if (!size.isValid() || + // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS + // XXX: it's likely that this check is unnecessary and the comment is + // wrong because we no longer use PR_Realloc outside of NSPR and NSS. + size.value() > UINT32_MAX || size.value() > mTotal) { + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_DIAGNOSTIC_ASSERT(mFileData); + MOZ_RELEASE_ASSERT((mDataLen + aCount) <= mTotal); + + nsresult rv = mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + mDataLen += bytesRead; + return NS_OK; +} + +// Helper methods + +void FileReader::ReadFileContent(Blob& aBlob, const nsAString& aCharset, + eDataFormat aDataFormat, ErrorResult& aRv) { + if (IsCurrentThreadRunningWorker() && !mWeakWorkerRef) { + // The worker is already shutting down. + return; + } + + if (mReadyState == LOADING) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + mError = nullptr; + + SetDOMStringToNull(mResult); + mResultArrayBuffer = nullptr; + + mAsyncStream = nullptr; + + mTransferred = 0; + mTotal = 0; + mReadyState = EMPTY; + FreeFileData(); + + mBlob = &aBlob; + mDataFormat = aDataFormat; + CopyUTF16toUTF8(aCharset, mCharset); + + { + nsCOMPtr<nsIInputStream> stream; + mBlob->CreateInputStream(getter_AddRefs(stream), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + aRv = NS_MakeAsyncNonBlockingInputStream(stream.forget(), + getter_AddRefs(mAsyncStream)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } + + MOZ_ASSERT(mAsyncStream); + + mTotal = mBlob->GetSize(aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + // Binary Format doesn't need a post-processing of the data. Everything is + // written directly into mResult. + if (mDataFormat != FILE_AS_BINARY) { + if (mDataFormat == FILE_AS_ARRAYBUFFER) { + mFileData = js_pod_malloc<char>(mTotal); + } else { + mFileData = (char*)malloc(mTotal); + } + + if (!mFileData) { + NS_WARNING("Preallocation failed for ReadFileData"); + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + } + + mAsyncWaitRunnable = new AsyncWaitRunnable(this); + aRv = NS_DispatchToCurrentThread(mAsyncWaitRunnable); + if (NS_WARN_IF(aRv.Failed())) { + FreeFileData(); + return; + } + + // FileReader should be in loading state here + mReadyState = LOADING; +} + +void FileReader::InitialAsyncWait() { + mAsyncWaitRunnable = nullptr; + + nsresult rv = DoAsyncWait(); + if (NS_WARN_IF(NS_FAILED(rv))) { + mReadyState = EMPTY; + FreeFileData(); + return; + } + + DispatchProgressEvent(nsLiteralString(LOADSTART_STR)); +} + +nsresult FileReader::GetAsText(Blob* aBlob, const nsACString& aCharset, + const char* aFileData, uint32_t aDataLen, + nsAString& aResult) { + // Try the API argument. + const Encoding* encoding = Encoding::ForLabel(aCharset); + if (!encoding) { + // API argument failed. Try the type property of the blob. + nsAutoString type16; + aBlob->GetType(type16); + NS_ConvertUTF16toUTF8 type(type16); + nsAutoCString specifiedCharset; + bool haveCharset; + int32_t charsetStart, charsetEnd; + NS_ExtractCharsetFromContentType(type, specifiedCharset, &haveCharset, + &charsetStart, &charsetEnd); + encoding = Encoding::ForLabel(specifiedCharset); + if (!encoding) { + // Type property failed. Use UTF-8. + encoding = UTF_8_ENCODING; + } + } + + auto data = Span(reinterpret_cast<const uint8_t*>(aFileData), aDataLen); + nsresult rv; + std::tie(rv, std::ignore) = encoding->Decode(data, aResult); + return NS_FAILED(rv) ? rv : NS_OK; +} + +nsresult FileReader::GetAsDataURL(Blob* aBlob, const char* aFileData, + uint32_t aDataLen, nsAString& aResult) { + aResult.AssignLiteral("data:"); + + nsAutoString contentType; + aBlob->GetType(contentType); + if (!contentType.IsEmpty()) { + aResult.Append(contentType); + } else { + aResult.AppendLiteral("application/octet-stream"); + } + aResult.AppendLiteral(";base64,"); + + return Base64EncodeAppend(aFileData, aDataLen, aResult); +} + +/* virtual */ +JSObject* FileReader::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return FileReader_Binding::Wrap(aCx, this, aGivenProto); +} + +void FileReader::StartProgressEventTimer() { + if (!NS_IsMainThread() && !mWeakWorkerRef) { + // The worker is possibly shutting down if dispatching a DOM event right + // before this call triggered an InterruptCallback call. + // XXX Note, the check is limited to workers for now, since it is unclear + // in the spec how FileReader should behave in this case on the main thread. + return; + } + + if (!mProgressNotifier) { + mProgressNotifier = NS_NewTimer(mTarget); + } + + if (mProgressNotifier) { + mProgressEventWasDelayed = false; + mTimerIsActive = true; + mProgressNotifier->Cancel(); + mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL, + nsITimer::TYPE_ONE_SHOT); + } +} + +void FileReader::ClearProgressEventTimer() { + mProgressEventWasDelayed = false; + mTimerIsActive = false; + if (mProgressNotifier) { + mProgressNotifier->Cancel(); + } +} + +void FileReader::FreeFileData() { + if (mFileData) { + if (mDataFormat == FILE_AS_ARRAYBUFFER) { + js_free(mFileData); + } else { + free(mFileData); + } + mFileData = nullptr; + } + + mDataLen = 0; +} + +void FileReader::FreeDataAndDispatchSuccess() { + FreeFileData(); + mResult.SetIsVoid(false); + mAsyncStream = nullptr; + mBlob = nullptr; + + // Dispatch event to signify end of a successful operation + DispatchProgressEvent(nsLiteralString(LOAD_STR)); + DispatchProgressEvent(nsLiteralString(LOADEND_STR)); +} + +void FileReader::FreeDataAndDispatchError() { + MOZ_ASSERT(mError); + + FreeFileData(); + mResult.SetIsVoid(true); + mAsyncStream = nullptr; + mBlob = nullptr; + + // Dispatch error event to signify load failure + DispatchProgressEvent(nsLiteralString(ERROR_STR)); + DispatchProgressEvent(nsLiteralString(LOADEND_STR)); +} + +void FileReader::FreeDataAndDispatchError(nsresult aRv) { + // Set the status attribute, and dispatch the error event + switch (aRv) { + case NS_ERROR_FILE_NOT_FOUND: + mError = DOMException::Create(NS_ERROR_DOM_NOT_FOUND_ERR); + break; + case NS_ERROR_FILE_ACCESS_DENIED: + mError = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR); + break; + default: + mError = DOMException::Create(NS_ERROR_DOM_FILE_NOT_READABLE_ERR); + break; + } + + FreeDataAndDispatchError(); +} + +nsresult FileReader::DispatchProgressEvent(const nsAString& aType) { + ProgressEventInit init; + init.mBubbles = false; + init.mCancelable = false; + init.mLoaded = mTransferred; + + if (mTotal != kUnknownSize) { + init.mLengthComputable = true; + init.mTotal = mTotal; + } else { + init.mLengthComputable = false; + init.mTotal = 0; + } + RefPtr<ProgressEvent> event = ProgressEvent::Constructor(this, aType, init); + event->SetTrusted(true); + + ErrorResult rv; + DispatchEvent(*event, rv); + return rv.StealNSResult(); +} + +// nsITimerCallback +NS_IMETHODIMP +FileReader::Notify(nsITimer* aTimer) { + nsresult rv; + mTimerIsActive = false; + + if (mProgressEventWasDelayed) { + rv = DispatchProgressEvent(u"progress"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + StartProgressEventTimer(); + } + + return NS_OK; +} + +// InputStreamCallback +NS_IMETHODIMP +FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream) { + // We use this class to decrease the busy counter at the end of this method. + // In theory we can do it immediatelly but, for debugging reasons, we want to + // be 100% sure we have a workerRef when OnLoadEnd() is called. + FileReaderDecreaseBusyCounter RAII(this); + + if (mReadyState != LOADING || aStream != mAsyncStream) { + return NS_OK; + } + + uint64_t count; + nsresult rv = aStream->Available(&count); + + if (NS_SUCCEEDED(rv) && count) { + rv = DoReadData(count); + + if (NS_SUCCEEDED(rv)) { + rv = DoAsyncWait(); + } + } + + if (NS_FAILED(rv) || !count) { + if (rv == NS_BASE_STREAM_CLOSED) { + rv = NS_OK; + } + OnLoadEnd(rv); + return NS_OK; + } + + mTransferred += count; + + // Notify the timer is the appropriate timeframe has passed + if (mTimerIsActive) { + mProgressEventWasDelayed = true; + } else { + rv = DispatchProgressEvent(nsLiteralString(PROGRESS_STR)); + NS_ENSURE_SUCCESS(rv, rv); + + StartProgressEventTimer(); + } + + return NS_OK; +} + +// nsINamed +NS_IMETHODIMP +FileReader::GetName(nsACString& aName) { + aName.AssignLiteral("FileReader"); + return NS_OK; +} + +void FileReader::OnLoadEnd(nsresult aStatus) { + // Cancel the progress event timer + ClearProgressEventTimer(); + + // FileReader must be in DONE stage after an operation + mReadyState = DONE; + + // Quick return, if failed. + if (NS_FAILED(aStatus)) { + FreeDataAndDispatchError(aStatus); + return; + } + + // In case we read a different number of bytes, we can assume that the + // underlying storage has changed. We should not continue. + if (mDataLen != mTotal) { + FreeDataAndDispatchError(NS_ERROR_FAILURE); + return; + } + + // ArrayBuffer needs a custom handling. + if (mDataFormat == FILE_AS_ARRAYBUFFER) { + OnLoadEndArrayBuffer(); + return; + } + + nsresult rv = NS_OK; + + // We don't do anything special for Binary format. + + if (mDataFormat == FILE_AS_DATAURL) { + rv = GetAsDataURL(mBlob, mFileData, mDataLen, mResult); + } else if (mDataFormat == FILE_AS_TEXT) { + if (!mFileData && mDataLen) { + rv = NS_ERROR_OUT_OF_MEMORY; + } else if (!mFileData) { + rv = GetAsText(mBlob, mCharset, "", mDataLen, mResult); + } else { + rv = GetAsText(mBlob, mCharset, mFileData, mDataLen, mResult); + } + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + FreeDataAndDispatchError(rv); + return; + } + + FreeDataAndDispatchSuccess(); +} + +void FileReader::Abort() { + if (mReadyState == EMPTY || mReadyState == DONE) { + return; + } + + MOZ_ASSERT(mReadyState == LOADING); + + Cleanup(); + + // XXX The spec doesn't say this + mError = DOMException::Create(NS_ERROR_DOM_ABORT_ERR); + + // Revert status and result attributes + SetDOMStringToNull(mResult); + mResultArrayBuffer = nullptr; + + mBlob = nullptr; + + // Dispatch the events + DispatchProgressEvent(nsLiteralString(ABORT_STR)); + DispatchProgressEvent(nsLiteralString(LOADEND_STR)); +} + +nsresult FileReader::IncreaseBusyCounter() { + if (mWeakWorkerRef && mBusyCount++ == 0) { + if (NS_WARN_IF(!mWeakWorkerRef->GetPrivate())) { + return NS_ERROR_FAILURE; + } + + RefPtr<FileReader> self = this; + + RefPtr<StrongWorkerRef> ref = + StrongWorkerRef::Create(mWeakWorkerRef->GetPrivate(), "FileReader", + [self]() { self->Shutdown(); }); + if (NS_WARN_IF(!ref)) { + return NS_ERROR_FAILURE; + } + + mStrongWorkerRef = ref; + } + + return NS_OK; +} + +void FileReader::DecreaseBusyCounter() { + MOZ_ASSERT_IF(mStrongWorkerRef, mBusyCount); + if (mStrongWorkerRef && --mBusyCount == 0) { + mStrongWorkerRef = nullptr; + } +} + +void FileReader::Cleanup() { + mReadyState = DONE; + + if (mAsyncWaitRunnable) { + mAsyncWaitRunnable->Cancel(); + mAsyncWaitRunnable = nullptr; + } + + if (mAsyncStream) { + mAsyncStream->Close(); + mAsyncStream = nullptr; + } + + ClearProgressEventTimer(); + FreeFileData(); + mResultArrayBuffer = nullptr; +} + +void FileReader::Shutdown() { + Cleanup(); + if (mWeakWorkerRef) { + mWeakWorkerRef = nullptr; + } +} + +} // namespace mozilla::dom |