Bug 1857841 - pt 3. Add a new page kind named "fresh" r=glandium
[gecko.git] / dom / file / FileReader.cpp
blob4a6d394fe1818379d12715a7f5bc6e08e9464e88
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "FileReader.h"
9 #include "nsIGlobalObject.h"
10 #include "nsITimer.h"
12 #include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
13 #include "mozilla/Base64.h"
14 #include "mozilla/CheckedInt.h"
15 #include "mozilla/dom/DOMException.h"
16 #include "mozilla/dom/DOMExceptionBinding.h"
17 #include "mozilla/dom/File.h"
18 #include "mozilla/dom/FileReaderBinding.h"
19 #include "mozilla/dom/ProgressEvent.h"
20 #include "mozilla/dom/UnionTypes.h"
21 #include "mozilla/dom/ScriptSettings.h"
22 #include "mozilla/dom/WorkerCommon.h"
23 #include "mozilla/dom/WorkerRef.h"
24 #include "mozilla/dom/WorkerScope.h"
25 #include "mozilla/Encoding.h"
26 #include "mozilla/HoldDropJSObjects.h"
27 #include "nsAlgorithm.h"
28 #include "nsCycleCollectionParticipant.h"
29 #include "nsDOMJSUtils.h"
30 #include "nsError.h"
31 #include "nsNetUtil.h"
32 #include "nsStreamUtils.h"
33 #include "nsThreadUtils.h"
34 #include "xpcpublic.h"
35 #include "nsReadableUtils.h"
37 namespace mozilla::dom {
39 #define ABORT_STR u"abort"
40 #define LOAD_STR u"load"
41 #define LOADSTART_STR u"loadstart"
42 #define LOADEND_STR u"loadend"
43 #define ERROR_STR u"error"
44 #define PROGRESS_STR u"progress"
46 const uint64_t kUnknownSize = uint64_t(-1);
48 NS_IMPL_CYCLE_COLLECTION_CLASS(FileReader)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileReader,
51 DOMEventTargetHelper)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
57 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader,
58 DOMEventTargetHelper)
59 tmp->Shutdown();
60 NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
63 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
64 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
66 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FileReader, DOMEventTargetHelper)
67 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
68 NS_IMPL_CYCLE_COLLECTION_TRACE_END
70 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileReader)
71 NS_INTERFACE_MAP_ENTRY_CONCRETE(FileReader)
72 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
73 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
74 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
75 NS_INTERFACE_MAP_ENTRY(nsINamed)
76 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
78 NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper)
79 NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper)
81 class MOZ_RAII FileReaderDecreaseBusyCounter {
82 RefPtr<FileReader> mFileReader;
84 public:
85 explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader)
86 : mFileReader(aFileReader) {}
88 ~FileReaderDecreaseBusyCounter() { mFileReader->DecreaseBusyCounter(); }
91 class FileReader::AsyncWaitRunnable final : public CancelableRunnable {
92 public:
93 explicit AsyncWaitRunnable(FileReader* aReader)
94 : CancelableRunnable("FileReader::AsyncWaitRunnable"), mReader(aReader) {}
96 NS_IMETHOD
97 Run() override {
98 if (mReader) {
99 mReader->InitialAsyncWait();
101 return NS_OK;
104 nsresult Cancel() override {
105 mReader = nullptr;
106 return NS_OK;
109 public:
110 RefPtr<FileReader> mReader;
113 void FileReader::RootResultArrayBuffer() { mozilla::HoldJSObjects(this); }
115 // FileReader constructors/initializers
117 FileReader::FileReader(nsIGlobalObject* aGlobal, WeakWorkerRef* aWorkerRef)
118 : DOMEventTargetHelper(aGlobal),
119 mFileData(nullptr),
120 mDataLen(0),
121 mDataFormat(FILE_AS_BINARY),
122 mResultArrayBuffer(nullptr),
123 mProgressEventWasDelayed(false),
124 mTimerIsActive(false),
125 mReadyState(EMPTY),
126 mTotal(0),
127 mTransferred(0),
128 mBusyCount(0),
129 mWeakWorkerRef(aWorkerRef) {
130 MOZ_ASSERT(aGlobal);
131 MOZ_ASSERT_IF(NS_IsMainThread(), !mWeakWorkerRef);
133 if (NS_IsMainThread()) {
134 mTarget = aGlobal->SerialEventTarget();
135 } else {
136 mTarget = GetCurrentSerialEventTarget();
139 SetDOMStringToNull(mResult);
142 FileReader::~FileReader() {
143 Shutdown();
144 DropJSObjects(this);
147 /* static */
148 already_AddRefed<FileReader> FileReader::Constructor(
149 const GlobalObject& aGlobal) {
150 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
151 RefPtr<WeakWorkerRef> workerRef;
153 if (!NS_IsMainThread()) {
154 JSContext* cx = aGlobal.Context();
155 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
157 workerRef = WeakWorkerRef::Create(workerPrivate);
160 RefPtr<FileReader> fileReader = new FileReader(global, workerRef);
162 return fileReader.forget();
165 // nsIInterfaceRequestor
167 NS_IMETHODIMP
168 FileReader::GetInterface(const nsIID& aIID, void** aResult) {
169 return QueryInterface(aIID, aResult);
172 void FileReader::GetResult(JSContext* aCx,
173 Nullable<OwningStringOrArrayBuffer>& aResult) {
174 JS::Rooted<JS::Value> result(aCx);
176 if (mDataFormat == FILE_AS_ARRAYBUFFER) {
177 if (mReadyState != DONE || !mResultArrayBuffer ||
178 !aResult.SetValue().SetAsArrayBuffer().Init(mResultArrayBuffer)) {
179 aResult.SetNull();
182 return;
185 if (mReadyState != DONE || mResult.IsVoid()) {
186 aResult.SetNull();
187 return;
190 aResult.SetValue().SetAsString() = mResult;
193 void FileReader::OnLoadEndArrayBuffer() {
194 AutoJSAPI jsapi;
195 if (!jsapi.Init(GetParentObject())) {
196 FreeDataAndDispatchError(NS_ERROR_FAILURE);
197 return;
200 RootResultArrayBuffer();
202 JSContext* cx = jsapi.cx();
204 // |mFileData| will be deallocated in FileReader's destructor when this
205 // ArrayBuffer allocation failed.
206 mResultArrayBuffer = JS::NewArrayBufferWithContents(
207 cx, mDataLen, mFileData,
208 JS::NewArrayBufferOutOfMemory::CallerMustFreeMemory);
209 if (mResultArrayBuffer) {
210 mFileData = nullptr; // Transfer ownership
211 FreeDataAndDispatchSuccess();
212 return;
215 // Let's handle the error status.
217 JS::Rooted<JS::Value> exceptionValue(cx);
218 if (!JS_GetPendingException(cx, &exceptionValue) ||
219 // This should not really happen, exception should always be an object.
220 !exceptionValue.isObject()) {
221 JS_ClearPendingException(jsapi.cx());
222 FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY);
223 return;
226 JS_ClearPendingException(jsapi.cx());
228 JS::Rooted<JSObject*> exceptionObject(cx, &exceptionValue.toObject());
229 JSErrorReport* er = JS_ErrorFromException(cx, exceptionObject);
230 if (!er || er->message()) {
231 FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY);
232 return;
235 nsAutoString errorName;
236 JSLinearString* name = js::GetErrorTypeName(cx, er->exnType);
237 if (name) {
238 AssignJSLinearString(errorName, name);
241 nsAutoCString errorMsg(er->message().c_str());
242 nsAutoCString errorNameC = NS_LossyConvertUTF16toASCII(errorName);
243 // XXX Code selected arbitrarily
244 mError =
245 new DOMException(NS_ERROR_DOM_INVALID_STATE_ERR, errorMsg, errorNameC,
246 DOMException_Binding::INVALID_STATE_ERR);
248 FreeDataAndDispatchError();
251 nsresult FileReader::DoAsyncWait() {
252 nsresult rv = IncreaseBusyCounter();
253 if (NS_WARN_IF(NS_FAILED(rv))) {
254 return rv;
257 rv = mAsyncStream->AsyncWait(this,
258 /* aFlags*/ 0,
259 /* aRequestedCount */ 0, mTarget);
260 if (NS_WARN_IF(NS_FAILED(rv))) {
261 DecreaseBusyCounter();
262 return rv;
265 return NS_OK;
268 namespace {
270 void PopulateBufferForBinaryString(char16_t* aDest, const char* aSource,
271 uint32_t aCount) {
272 // Zero-extend each char to char16_t.
273 ConvertLatin1toUtf16(Span(aSource, aCount), Span(aDest, aCount));
276 nsresult ReadFuncBinaryString(nsIInputStream* aInputStream, void* aClosure,
277 const char* aFromRawSegment, uint32_t aToOffset,
278 uint32_t aCount, uint32_t* aWriteCount) {
279 char16_t* dest = static_cast<char16_t*>(aClosure) + aToOffset;
280 PopulateBufferForBinaryString(dest, aFromRawSegment, aCount);
281 *aWriteCount = aCount;
282 return NS_OK;
285 } // namespace
287 nsresult FileReader::DoReadData(uint64_t aCount) {
288 MOZ_ASSERT(mAsyncStream);
290 uint32_t bytesRead = 0;
292 if (mDataFormat == FILE_AS_BINARY) {
293 // Continuously update our binary string as data comes in
294 CheckedInt<uint64_t> size{mResult.Length()};
295 size += aCount;
297 if (!size.isValid() || size.value() > UINT32_MAX || size.value() > mTotal) {
298 return NS_ERROR_OUT_OF_MEMORY;
301 uint32_t lenBeforeRead = mResult.Length();
302 MOZ_ASSERT(lenBeforeRead == mDataLen, "unexpected mResult length");
304 mResult.SetLength(lenBeforeRead + aCount);
305 char16_t* currentPos = mResult.BeginWriting() + lenBeforeRead;
307 if (NS_InputStreamIsBuffered(mAsyncStream)) {
308 nsresult rv = mAsyncStream->ReadSegments(ReadFuncBinaryString, currentPos,
309 aCount, &bytesRead);
310 NS_ENSURE_SUCCESS(rv, NS_OK);
311 } else {
312 while (aCount > 0) {
313 char tmpBuffer[4096];
314 uint32_t minCount =
315 XPCOM_MIN(aCount, static_cast<uint64_t>(sizeof(tmpBuffer)));
316 uint32_t read = 0;
318 nsresult rv = mAsyncStream->Read(tmpBuffer, minCount, &read);
319 if (rv == NS_BASE_STREAM_CLOSED) {
320 rv = NS_OK;
323 NS_ENSURE_SUCCESS(rv, NS_OK);
325 if (read == 0) {
326 // The stream finished too early.
327 return NS_ERROR_OUT_OF_MEMORY;
330 PopulateBufferForBinaryString(currentPos, tmpBuffer, read);
332 currentPos += read;
333 aCount -= read;
334 bytesRead += read;
338 MOZ_ASSERT(size.value() == lenBeforeRead + bytesRead);
339 mResult.Truncate(size.value());
340 } else {
341 CheckedInt<uint64_t> size = mDataLen;
342 size += aCount;
344 // Update memory buffer to reflect the contents of the file
345 if (!size.isValid() ||
346 // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
347 // XXX: it's likely that this check is unnecessary and the comment is
348 // wrong because we no longer use PR_Realloc outside of NSPR and NSS.
349 size.value() > UINT32_MAX || size.value() > mTotal) {
350 return NS_ERROR_OUT_OF_MEMORY;
353 MOZ_DIAGNOSTIC_ASSERT(mFileData);
354 MOZ_RELEASE_ASSERT((mDataLen + aCount) <= mTotal);
356 nsresult rv = mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
357 if (NS_WARN_IF(NS_FAILED(rv))) {
358 return rv;
362 mDataLen += bytesRead;
363 return NS_OK;
366 // Helper methods
368 void FileReader::ReadFileContent(Blob& aBlob, const nsAString& aCharset,
369 eDataFormat aDataFormat, ErrorResult& aRv) {
370 if (IsCurrentThreadRunningWorker() && !mWeakWorkerRef) {
371 // The worker is already shutting down.
372 return;
375 if (mReadyState == LOADING) {
376 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
377 return;
380 mError = nullptr;
382 SetDOMStringToNull(mResult);
383 mResultArrayBuffer = nullptr;
385 mAsyncStream = nullptr;
387 mTransferred = 0;
388 mTotal = 0;
389 mReadyState = EMPTY;
390 FreeFileData();
392 mBlob = &aBlob;
393 mDataFormat = aDataFormat;
394 CopyUTF16toUTF8(aCharset, mCharset);
397 nsCOMPtr<nsIInputStream> stream;
398 mBlob->CreateInputStream(getter_AddRefs(stream), aRv);
399 if (NS_WARN_IF(aRv.Failed())) {
400 return;
403 aRv = NS_MakeAsyncNonBlockingInputStream(stream.forget(),
404 getter_AddRefs(mAsyncStream));
405 if (NS_WARN_IF(aRv.Failed())) {
406 return;
410 MOZ_ASSERT(mAsyncStream);
412 mTotal = mBlob->GetSize(aRv);
413 if (NS_WARN_IF(aRv.Failed())) {
414 return;
417 // Binary Format doesn't need a post-processing of the data. Everything is
418 // written directly into mResult.
419 if (mDataFormat != FILE_AS_BINARY) {
420 if (mDataFormat == FILE_AS_ARRAYBUFFER) {
421 mFileData = js_pod_malloc<char>(mTotal);
422 } else {
423 mFileData = (char*)malloc(mTotal);
426 if (!mFileData) {
427 NS_WARNING("Preallocation failed for ReadFileData");
428 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
429 return;
433 mAsyncWaitRunnable = new AsyncWaitRunnable(this);
434 aRv = NS_DispatchToCurrentThread(mAsyncWaitRunnable);
435 if (NS_WARN_IF(aRv.Failed())) {
436 FreeFileData();
437 return;
440 // FileReader should be in loading state here
441 mReadyState = LOADING;
444 void FileReader::InitialAsyncWait() {
445 mAsyncWaitRunnable = nullptr;
447 nsresult rv = DoAsyncWait();
448 if (NS_WARN_IF(NS_FAILED(rv))) {
449 mReadyState = EMPTY;
450 FreeFileData();
451 return;
454 DispatchProgressEvent(nsLiteralString(LOADSTART_STR));
457 nsresult FileReader::GetAsText(Blob* aBlob, const nsACString& aCharset,
458 const char* aFileData, uint32_t aDataLen,
459 nsAString& aResult) {
460 // Try the API argument.
461 const Encoding* encoding = Encoding::ForLabel(aCharset);
462 if (!encoding) {
463 // API argument failed. Try the type property of the blob.
464 nsAutoString type16;
465 aBlob->GetType(type16);
466 NS_ConvertUTF16toUTF8 type(type16);
467 nsAutoCString specifiedCharset;
468 bool haveCharset;
469 int32_t charsetStart, charsetEnd;
470 NS_ExtractCharsetFromContentType(type, specifiedCharset, &haveCharset,
471 &charsetStart, &charsetEnd);
472 encoding = Encoding::ForLabel(specifiedCharset);
473 if (!encoding) {
474 // Type property failed. Use UTF-8.
475 encoding = UTF_8_ENCODING;
479 auto data = Span(reinterpret_cast<const uint8_t*>(aFileData), aDataLen);
480 nsresult rv;
481 std::tie(rv, std::ignore) = encoding->Decode(data, aResult);
482 return NS_FAILED(rv) ? rv : NS_OK;
485 nsresult FileReader::GetAsDataURL(Blob* aBlob, const char* aFileData,
486 uint32_t aDataLen, nsAString& aResult) {
487 aResult.AssignLiteral("data:");
489 nsAutoString contentType;
490 aBlob->GetType(contentType);
491 if (!contentType.IsEmpty()) {
492 aResult.Append(contentType);
493 } else {
494 aResult.AppendLiteral("application/octet-stream");
496 aResult.AppendLiteral(";base64,");
498 return Base64EncodeAppend(aFileData, aDataLen, aResult);
501 /* virtual */
502 JSObject* FileReader::WrapObject(JSContext* aCx,
503 JS::Handle<JSObject*> aGivenProto) {
504 return FileReader_Binding::Wrap(aCx, this, aGivenProto);
507 void FileReader::StartProgressEventTimer() {
508 if (!NS_IsMainThread() && !mWeakWorkerRef) {
509 // The worker is possibly shutting down if dispatching a DOM event right
510 // before this call triggered an InterruptCallback call.
511 // XXX Note, the check is limited to workers for now, since it is unclear
512 // in the spec how FileReader should behave in this case on the main thread.
513 return;
516 if (!mProgressNotifier) {
517 mProgressNotifier = NS_NewTimer(mTarget);
520 if (mProgressNotifier) {
521 mProgressEventWasDelayed = false;
522 mTimerIsActive = true;
523 mProgressNotifier->Cancel();
524 mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
525 nsITimer::TYPE_ONE_SHOT);
529 void FileReader::ClearProgressEventTimer() {
530 mProgressEventWasDelayed = false;
531 mTimerIsActive = false;
532 if (mProgressNotifier) {
533 mProgressNotifier->Cancel();
537 void FileReader::FreeFileData() {
538 if (mFileData) {
539 if (mDataFormat == FILE_AS_ARRAYBUFFER) {
540 js_free(mFileData);
541 } else {
542 free(mFileData);
544 mFileData = nullptr;
547 mDataLen = 0;
550 void FileReader::FreeDataAndDispatchSuccess() {
551 FreeFileData();
552 mResult.SetIsVoid(false);
553 mAsyncStream = nullptr;
554 mBlob = nullptr;
556 // Dispatch event to signify end of a successful operation
557 DispatchProgressEvent(nsLiteralString(LOAD_STR));
558 DispatchProgressEvent(nsLiteralString(LOADEND_STR));
561 void FileReader::FreeDataAndDispatchError() {
562 MOZ_ASSERT(mError);
564 FreeFileData();
565 mResult.SetIsVoid(true);
566 mAsyncStream = nullptr;
567 mBlob = nullptr;
569 // Dispatch error event to signify load failure
570 DispatchProgressEvent(nsLiteralString(ERROR_STR));
571 DispatchProgressEvent(nsLiteralString(LOADEND_STR));
574 void FileReader::FreeDataAndDispatchError(nsresult aRv) {
575 // Set the status attribute, and dispatch the error event
576 switch (aRv) {
577 case NS_ERROR_FILE_NOT_FOUND:
578 mError = DOMException::Create(NS_ERROR_DOM_NOT_FOUND_ERR);
579 break;
580 case NS_ERROR_FILE_ACCESS_DENIED:
581 mError = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
582 break;
583 default:
584 mError = DOMException::Create(NS_ERROR_DOM_FILE_NOT_READABLE_ERR);
585 break;
588 FreeDataAndDispatchError();
591 nsresult FileReader::DispatchProgressEvent(const nsAString& aType) {
592 ProgressEventInit init;
593 init.mBubbles = false;
594 init.mCancelable = false;
595 init.mLoaded = mTransferred;
597 if (mTotal != kUnknownSize) {
598 init.mLengthComputable = true;
599 init.mTotal = mTotal;
600 } else {
601 init.mLengthComputable = false;
602 init.mTotal = 0;
604 RefPtr<ProgressEvent> event = ProgressEvent::Constructor(this, aType, init);
605 event->SetTrusted(true);
607 ErrorResult rv;
608 DispatchEvent(*event, rv);
609 return rv.StealNSResult();
612 // nsITimerCallback
613 NS_IMETHODIMP
614 FileReader::Notify(nsITimer* aTimer) {
615 nsresult rv;
616 mTimerIsActive = false;
618 if (mProgressEventWasDelayed) {
619 rv = DispatchProgressEvent(u"progress"_ns);
620 NS_ENSURE_SUCCESS(rv, rv);
622 StartProgressEventTimer();
625 return NS_OK;
628 // InputStreamCallback
629 NS_IMETHODIMP
630 FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream) {
631 // We use this class to decrease the busy counter at the end of this method.
632 // In theory we can do it immediatelly but, for debugging reasons, we want to
633 // be 100% sure we have a workerRef when OnLoadEnd() is called.
634 FileReaderDecreaseBusyCounter RAII(this);
636 if (mReadyState != LOADING || aStream != mAsyncStream) {
637 return NS_OK;
640 uint64_t count;
641 nsresult rv = aStream->Available(&count);
643 if (NS_SUCCEEDED(rv) && count) {
644 rv = DoReadData(count);
646 if (NS_SUCCEEDED(rv)) {
647 rv = DoAsyncWait();
651 if (NS_FAILED(rv) || !count) {
652 if (rv == NS_BASE_STREAM_CLOSED) {
653 rv = NS_OK;
655 OnLoadEnd(rv);
656 return NS_OK;
659 mTransferred += count;
661 // Notify the timer is the appropriate timeframe has passed
662 if (mTimerIsActive) {
663 mProgressEventWasDelayed = true;
664 } else {
665 rv = DispatchProgressEvent(nsLiteralString(PROGRESS_STR));
666 NS_ENSURE_SUCCESS(rv, rv);
668 StartProgressEventTimer();
671 return NS_OK;
674 // nsINamed
675 NS_IMETHODIMP
676 FileReader::GetName(nsACString& aName) {
677 aName.AssignLiteral("FileReader");
678 return NS_OK;
681 void FileReader::OnLoadEnd(nsresult aStatus) {
682 // Cancel the progress event timer
683 ClearProgressEventTimer();
685 // FileReader must be in DONE stage after an operation
686 mReadyState = DONE;
688 // Quick return, if failed.
689 if (NS_FAILED(aStatus)) {
690 FreeDataAndDispatchError(aStatus);
691 return;
694 // In case we read a different number of bytes, we can assume that the
695 // underlying storage has changed. We should not continue.
696 if (mDataLen != mTotal) {
697 FreeDataAndDispatchError(NS_ERROR_FAILURE);
698 return;
701 // ArrayBuffer needs a custom handling.
702 if (mDataFormat == FILE_AS_ARRAYBUFFER) {
703 OnLoadEndArrayBuffer();
704 return;
707 nsresult rv = NS_OK;
709 // We don't do anything special for Binary format.
711 if (mDataFormat == FILE_AS_DATAURL) {
712 rv = GetAsDataURL(mBlob, mFileData, mDataLen, mResult);
713 } else if (mDataFormat == FILE_AS_TEXT) {
714 if (!mFileData && mDataLen) {
715 rv = NS_ERROR_OUT_OF_MEMORY;
716 } else if (!mFileData) {
717 rv = GetAsText(mBlob, mCharset, "", mDataLen, mResult);
718 } else {
719 rv = GetAsText(mBlob, mCharset, mFileData, mDataLen, mResult);
723 if (NS_WARN_IF(NS_FAILED(rv))) {
724 FreeDataAndDispatchError(rv);
725 return;
728 FreeDataAndDispatchSuccess();
731 void FileReader::Abort() {
732 if (mReadyState == EMPTY || mReadyState == DONE) {
733 return;
736 MOZ_ASSERT(mReadyState == LOADING);
738 Cleanup();
740 // XXX The spec doesn't say this
741 mError = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
743 // Revert status and result attributes
744 SetDOMStringToNull(mResult);
745 mResultArrayBuffer = nullptr;
747 mBlob = nullptr;
749 // Dispatch the events
750 DispatchProgressEvent(nsLiteralString(ABORT_STR));
751 DispatchProgressEvent(nsLiteralString(LOADEND_STR));
754 nsresult FileReader::IncreaseBusyCounter() {
755 if (mWeakWorkerRef && mBusyCount++ == 0) {
756 if (NS_WARN_IF(!mWeakWorkerRef->GetPrivate())) {
757 return NS_ERROR_FAILURE;
760 RefPtr<FileReader> self = this;
762 RefPtr<StrongWorkerRef> ref =
763 StrongWorkerRef::Create(mWeakWorkerRef->GetPrivate(), "FileReader",
764 [self]() { self->Shutdown(); });
765 if (NS_WARN_IF(!ref)) {
766 return NS_ERROR_FAILURE;
769 mStrongWorkerRef = ref;
772 return NS_OK;
775 void FileReader::DecreaseBusyCounter() {
776 MOZ_ASSERT_IF(mStrongWorkerRef, mBusyCount);
777 if (mStrongWorkerRef && --mBusyCount == 0) {
778 mStrongWorkerRef = nullptr;
782 void FileReader::Cleanup() {
783 mReadyState = DONE;
785 if (mAsyncWaitRunnable) {
786 mAsyncWaitRunnable->Cancel();
787 mAsyncWaitRunnable = nullptr;
790 if (mAsyncStream) {
791 mAsyncStream->Close();
792 mAsyncStream = nullptr;
795 ClearProgressEventTimer();
796 FreeFileData();
797 mResultArrayBuffer = nullptr;
800 void FileReader::Shutdown() {
801 Cleanup();
802 if (mWeakWorkerRef) {
803 mWeakWorkerRef = nullptr;
807 } // namespace mozilla::dom