/* -*- 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 "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->SerialEventTarget();
}
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();
// |mFileData| will be deallocated in FileReader's destructor when this
// ArrayBuffer allocation failed.
mResultArrayBuffer = JS::NewArrayBufferWithContents(
cx, mDataLen, mFileData,
JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
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 =
std::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