mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
ace6d1063f
The inclusions were removed with the following very crude script and the resulting breakage was fixed up by hand. The manual fixups did either revert the changes done by the script, replace a generic header with a more specific one or replace a header with a forward declaration. find . -name "*.idl" | grep -v web-platform | grep -v third_party | while read path; do interfaces=$(grep "^\(class\|interface\).*:.*" "$path" | cut -d' ' -f2) if [ -n "$interfaces" ]; then if [[ "$interfaces" == *$'\n'* ]]; then regexp="\(" for i in $interfaces; do regexp="$regexp$i\|"; done regexp="${regexp%%\\\|}\)" else regexp="$interfaces" fi interface=$(basename "$path") rg -l "#include.*${interface%%.idl}.h" . | while read path2; do hits=$(grep -v "#include.*${interface%%.idl}.h" "$path2" | grep -c "$regexp" ) if [ $hits -eq 0 ]; then echo "Removing ${interface} from ${path2}" grep -v "#include.*${interface%%.idl}.h" "$path2" > "$path2".tmp mv -f "$path2".tmp "$path2" fi done fi done Differential Revision: https://phabricator.services.mozilla.com/D55442 --HG-- extra : moz-landing-system : lando
767 lines
20 KiB
C++
767 lines
20 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 "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/WorkerCommon.h"
|
|
#include "mozilla/dom/WorkerRef.h"
|
|
#include "mozilla/dom/WorkerScope.h"
|
|
#include "mozilla/Encoding.h"
|
|
#include "nsAlgorithm.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsDOMJSUtils.h"
|
|
#include "nsError.h"
|
|
#include "nsNetUtil.h"
|
|
#include "xpcpublic.h"
|
|
#include "nsReadableUtils.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
#define ABORT_STR "abort"
|
|
#define LOAD_STR "load"
|
|
#define LOADSTART_STR "loadstart"
|
|
#define LOADEND_STR "loadend"
|
|
#define ERROR_STR "error"
|
|
#define PROGRESS_STR "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_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(); }
|
|
};
|
|
|
|
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 = GetCurrentThreadSerialEventTarget();
|
|
}
|
|
|
|
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 (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(MakeSpan(aSource, aCount), MakeSpan(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 oldLen = mResult.Length();
|
|
MOZ_ASSERT(oldLen == mDataLen, "unexpected mResult length");
|
|
|
|
char16_t* dest = nullptr;
|
|
mResult.GetMutableData(&dest, size.value(), fallible);
|
|
NS_ENSURE_TRUE(dest, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
dest += oldLen;
|
|
|
|
if (NS_InputStreamIsBuffered(mAsyncStream)) {
|
|
nsresult rv = mAsyncStream->ReadSegments(ReadFuncBinaryString, dest,
|
|
aCount, &bytesRead);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
} else {
|
|
while (aCount > 0) {
|
|
char tmpBuffer[4096];
|
|
uint32_t minCount =
|
|
XPCOM_MIN(aCount, static_cast<uint64_t>(sizeof(tmpBuffer)));
|
|
uint32_t read;
|
|
|
|
nsresult rv = mAsyncStream->Read(tmpBuffer, minCount, &read);
|
|
if (rv == NS_BASE_STREAM_CLOSED) {
|
|
rv = NS_OK;
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (read == 0) {
|
|
// The stream finished too early.
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
PopulateBufferForBinaryString(dest, tmpBuffer, read);
|
|
|
|
dest += read;
|
|
aCount -= read;
|
|
bytesRead += read;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(size.value() == oldLen + 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;
|
|
}
|
|
}
|
|
|
|
aRv = DoAsyncWait();
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
FreeFileData();
|
|
return;
|
|
}
|
|
|
|
// FileReader should be in loading state here
|
|
mReadyState = LOADING;
|
|
DispatchProgressEvent(NS_LITERAL_STRING(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 = MakeSpan(reinterpret_cast<const uint8_t*>(aFileData), aDataLen);
|
|
nsresult rv;
|
|
Tie(rv, encoding) = 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,");
|
|
|
|
nsCString encodedData;
|
|
nsresult rv = Base64Encode(Substring(aFileData, aDataLen), encodedData);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!AppendASCIItoUTF16(encodedData, aResult, fallible)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* virtual */
|
|
JSObject* FileReader::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return FileReader_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
void FileReader::StartProgressEventTimer() {
|
|
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(NS_LITERAL_STRING(LOAD_STR));
|
|
DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
|
|
}
|
|
|
|
void FileReader::FreeDataAndDispatchError() {
|
|
MOZ_ASSERT(mError);
|
|
|
|
FreeFileData();
|
|
mResult.SetIsVoid(true);
|
|
mAsyncStream = nullptr;
|
|
mBlob = nullptr;
|
|
|
|
// Dispatch error event to signify load failure
|
|
DispatchProgressEvent(NS_LITERAL_STRING(ERROR_STR));
|
|
DispatchProgressEvent(NS_LITERAL_STRING(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(NS_LITERAL_STRING("progress"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
StartProgressEventTimer();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// InputStreamCallback
|
|
NS_IMETHODIMP
|
|
FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream) {
|
|
if (mReadyState != LOADING || aStream != mAsyncStream) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// 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);
|
|
|
|
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(NS_LITERAL_STRING(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);
|
|
|
|
ClearProgressEventTimer();
|
|
|
|
mReadyState = DONE;
|
|
|
|
// XXX The spec doesn't say this
|
|
mError = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
|
|
|
|
// Revert status and result attributes
|
|
SetDOMStringToNull(mResult);
|
|
mResultArrayBuffer = nullptr;
|
|
|
|
mAsyncStream = nullptr;
|
|
mBlob = nullptr;
|
|
|
|
// Clean up memory buffer
|
|
FreeFileData();
|
|
|
|
// Dispatch the events
|
|
DispatchProgressEvent(NS_LITERAL_STRING(ABORT_STR));
|
|
DispatchProgressEvent(NS_LITERAL_STRING(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::Shutdown() {
|
|
mReadyState = DONE;
|
|
|
|
if (mAsyncStream) {
|
|
mAsyncStream->Close();
|
|
mAsyncStream = nullptr;
|
|
}
|
|
|
|
FreeFileData();
|
|
mResultArrayBuffer = nullptr;
|
|
|
|
if (mWeakWorkerRef && mBusyCount != 0) {
|
|
mStrongWorkerRef = nullptr;
|
|
mWeakWorkerRef = nullptr;
|
|
mBusyCount = 0;
|
|
}
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|