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/. */
6 
7 #include "FileReader.h"
8 
9 #include "nsIGlobalObject.h"
10 #include "nsITimer.h"
11 
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/WorkerCommon.h"
21 #include "mozilla/dom/WorkerRef.h"
22 #include "mozilla/dom/WorkerScope.h"
23 #include "mozilla/Encoding.h"
24 #include "nsAlgorithm.h"
25 #include "nsCycleCollectionParticipant.h"
26 #include "nsDOMJSUtils.h"
27 #include "nsError.h"
28 #include "nsNetUtil.h"
29 #include "xpcpublic.h"
30 #include "nsReadableUtils.h"
31 
32 namespace mozilla {
33 namespace dom {
34 
35 #define ABORT_STR "abort"
36 #define LOAD_STR "load"
37 #define LOADSTART_STR "loadstart"
38 #define LOADEND_STR "loadend"
39 #define ERROR_STR "error"
40 #define PROGRESS_STR "progress"
41 
42 const uint64_t kUnknownSize = uint64_t(-1);
43 
44 NS_IMPL_CYCLE_COLLECTION_CLASS(FileReader)
45 
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileReader,
47                                                   DOMEventTargetHelper)
48   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
49   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier)
50   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
52 
53 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader,
54                                                 DOMEventTargetHelper)
55   tmp->Shutdown();
56   NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
57   NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier)
58   NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
59   NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
60 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
61 
62 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FileReader, DOMEventTargetHelper)
63   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
64 NS_IMPL_CYCLE_COLLECTION_TRACE_END
65 
66 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FileReader)
67   NS_INTERFACE_MAP_ENTRY_CONCRETE(FileReader)
68   NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
69   NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
70   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
71   NS_INTERFACE_MAP_ENTRY(nsINamed)
72 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
73 
74 NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper)
75 NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper)
76 
77 class MOZ_RAII FileReaderDecreaseBusyCounter {
78   RefPtr<FileReader> mFileReader;
79 
80  public:
FileReaderDecreaseBusyCounter(FileReader * aFileReader)81   explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader)
82       : mFileReader(aFileReader) {}
83 
~FileReaderDecreaseBusyCounter()84   ~FileReaderDecreaseBusyCounter() { mFileReader->DecreaseBusyCounter(); }
85 };
86 
RootResultArrayBuffer()87 void FileReader::RootResultArrayBuffer() { mozilla::HoldJSObjects(this); }
88 
89 // FileReader constructors/initializers
90 
FileReader(nsIGlobalObject * aGlobal,WeakWorkerRef * aWorkerRef)91 FileReader::FileReader(nsIGlobalObject* aGlobal, WeakWorkerRef* aWorkerRef)
92     : DOMEventTargetHelper(aGlobal),
93       mFileData(nullptr),
94       mDataLen(0),
95       mDataFormat(FILE_AS_BINARY),
96       mResultArrayBuffer(nullptr),
97       mProgressEventWasDelayed(false),
98       mTimerIsActive(false),
99       mReadyState(EMPTY),
100       mTotal(0),
101       mTransferred(0),
102       mBusyCount(0),
103       mWeakWorkerRef(aWorkerRef) {
104   MOZ_ASSERT(aGlobal);
105   MOZ_ASSERT_IF(NS_IsMainThread(), !mWeakWorkerRef);
106 
107   if (NS_IsMainThread()) {
108     mTarget = aGlobal->EventTargetFor(TaskCategory::Other);
109   } else {
110     mTarget = GetCurrentThreadSerialEventTarget();
111   }
112 
113   SetDOMStringToNull(mResult);
114 }
115 
~FileReader()116 FileReader::~FileReader() {
117   Shutdown();
118   DropJSObjects(this);
119 }
120 
121 /* static */
Constructor(const GlobalObject & aGlobal)122 already_AddRefed<FileReader> FileReader::Constructor(
123     const GlobalObject& aGlobal) {
124   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
125   RefPtr<WeakWorkerRef> workerRef;
126 
127   if (!NS_IsMainThread()) {
128     JSContext* cx = aGlobal.Context();
129     WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
130 
131     workerRef = WeakWorkerRef::Create(workerPrivate);
132   }
133 
134   RefPtr<FileReader> fileReader = new FileReader(global, workerRef);
135 
136   return fileReader.forget();
137 }
138 
139 // nsIInterfaceRequestor
140 
141 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)142 FileReader::GetInterface(const nsIID& aIID, void** aResult) {
143   return QueryInterface(aIID, aResult);
144 }
145 
GetResult(JSContext * aCx,Nullable<OwningStringOrArrayBuffer> & aResult)146 void FileReader::GetResult(JSContext* aCx,
147                            Nullable<OwningStringOrArrayBuffer>& aResult) {
148   JS::Rooted<JS::Value> result(aCx);
149 
150   if (mDataFormat == FILE_AS_ARRAYBUFFER) {
151     if (mReadyState != DONE || !mResultArrayBuffer ||
152         !aResult.SetValue().SetAsArrayBuffer().Init(mResultArrayBuffer)) {
153       aResult.SetNull();
154     }
155 
156     return;
157   }
158 
159   if (mResult.IsVoid()) {
160     aResult.SetNull();
161     return;
162   }
163 
164   aResult.SetValue().SetAsString() = mResult;
165 }
166 
OnLoadEndArrayBuffer()167 void FileReader::OnLoadEndArrayBuffer() {
168   AutoJSAPI jsapi;
169   if (!jsapi.Init(GetParentObject())) {
170     FreeDataAndDispatchError(NS_ERROR_FAILURE);
171     return;
172   }
173 
174   RootResultArrayBuffer();
175 
176   JSContext* cx = jsapi.cx();
177 
178   mResultArrayBuffer = JS::NewArrayBufferWithContents(cx, mDataLen, mFileData);
179   if (mResultArrayBuffer) {
180     mFileData = nullptr;  // Transfer ownership
181     FreeDataAndDispatchSuccess();
182     return;
183   }
184 
185   // Let's handle the error status.
186 
187   JS::Rooted<JS::Value> exceptionValue(cx);
188   if (!JS_GetPendingException(cx, &exceptionValue) ||
189       // This should not really happen, exception should always be an object.
190       !exceptionValue.isObject()) {
191     JS_ClearPendingException(jsapi.cx());
192     FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY);
193     return;
194   }
195 
196   JS_ClearPendingException(jsapi.cx());
197 
198   JS::Rooted<JSObject*> exceptionObject(cx, &exceptionValue.toObject());
199   JSErrorReport* er = JS_ErrorFromException(cx, exceptionObject);
200   if (!er || er->message()) {
201     FreeDataAndDispatchError(NS_ERROR_OUT_OF_MEMORY);
202     return;
203   }
204 
205   nsAutoString errorName;
206   JSLinearString* name = js::GetErrorTypeName(cx, er->exnType);
207   if (name) {
208     AssignJSLinearString(errorName, name);
209   }
210 
211   nsAutoCString errorMsg(er->message().c_str());
212   nsAutoCString errorNameC = NS_LossyConvertUTF16toASCII(errorName);
213   // XXX Code selected arbitrarily
214   mError =
215       new DOMException(NS_ERROR_DOM_INVALID_STATE_ERR, errorMsg, errorNameC,
216                        DOMException_Binding::INVALID_STATE_ERR);
217 
218   FreeDataAndDispatchError();
219 }
220 
DoAsyncWait()221 nsresult FileReader::DoAsyncWait() {
222   nsresult rv = IncreaseBusyCounter();
223   if (NS_WARN_IF(NS_FAILED(rv))) {
224     return rv;
225   }
226 
227   rv = mAsyncStream->AsyncWait(this,
228                                /* aFlags*/ 0,
229                                /* aRequestedCount */ 0, mTarget);
230   if (NS_WARN_IF(NS_FAILED(rv))) {
231     DecreaseBusyCounter();
232     return rv;
233   }
234 
235   return NS_OK;
236 }
237 
238 namespace {
239 
PopulateBufferForBinaryString(char16_t * aDest,const char * aSource,uint32_t aCount)240 void PopulateBufferForBinaryString(char16_t* aDest, const char* aSource,
241                                    uint32_t aCount) {
242   // Zero-extend each char to char16_t.
243   ConvertLatin1toUtf16(MakeSpan(aSource, aCount), MakeSpan(aDest, aCount));
244 }
245 
ReadFuncBinaryString(nsIInputStream * aInputStream,void * aClosure,const char * aFromRawSegment,uint32_t aToOffset,uint32_t aCount,uint32_t * aWriteCount)246 nsresult ReadFuncBinaryString(nsIInputStream* aInputStream, void* aClosure,
247                               const char* aFromRawSegment, uint32_t aToOffset,
248                               uint32_t aCount, uint32_t* aWriteCount) {
249   char16_t* dest = static_cast<char16_t*>(aClosure) + aToOffset;
250   PopulateBufferForBinaryString(dest, aFromRawSegment, aCount);
251   *aWriteCount = aCount;
252   return NS_OK;
253 }
254 
255 }  // namespace
256 
DoReadData(uint64_t aCount)257 nsresult FileReader::DoReadData(uint64_t aCount) {
258   MOZ_ASSERT(mAsyncStream);
259 
260   uint32_t bytesRead = 0;
261 
262   if (mDataFormat == FILE_AS_BINARY) {
263     // Continuously update our binary string as data comes in
264     CheckedInt<uint64_t> size{mResult.Length()};
265     size += aCount;
266 
267     if (!size.isValid() || size.value() > UINT32_MAX || size.value() > mTotal) {
268       return NS_ERROR_OUT_OF_MEMORY;
269     }
270 
271     uint32_t lenBeforeRead = mResult.Length();
272     MOZ_ASSERT(lenBeforeRead == mDataLen, "unexpected mResult length");
273 
274     mResult.SetLength(lenBeforeRead + aCount);
275     char16_t* currentPos = mResult.BeginWriting() + lenBeforeRead;
276 
277     if (NS_InputStreamIsBuffered(mAsyncStream)) {
278       nsresult rv = mAsyncStream->ReadSegments(ReadFuncBinaryString, currentPos,
279                                                aCount, &bytesRead);
280       NS_ENSURE_SUCCESS(rv, NS_OK);
281     } else {
282       while (aCount > 0) {
283         char tmpBuffer[4096];
284         uint32_t minCount =
285             XPCOM_MIN(aCount, static_cast<uint64_t>(sizeof(tmpBuffer)));
286         uint32_t read;
287 
288         nsresult rv = mAsyncStream->Read(tmpBuffer, minCount, &read);
289         if (rv == NS_BASE_STREAM_CLOSED) {
290           rv = NS_OK;
291         }
292 
293         NS_ENSURE_SUCCESS(rv, NS_OK);
294 
295         if (read == 0) {
296           // The stream finished too early.
297           return NS_ERROR_OUT_OF_MEMORY;
298         }
299 
300         PopulateBufferForBinaryString(currentPos, tmpBuffer, read);
301 
302         currentPos += read;
303         aCount -= read;
304         bytesRead += read;
305       }
306     }
307 
308     MOZ_ASSERT(size.value() == lenBeforeRead + bytesRead);
309     mResult.Truncate(size.value());
310   } else {
311     CheckedInt<uint64_t> size = mDataLen;
312     size += aCount;
313 
314     // Update memory buffer to reflect the contents of the file
315     if (!size.isValid() ||
316         // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
317         // XXX: it's likely that this check is unnecessary and the comment is
318         // wrong because we no longer use PR_Realloc outside of NSPR and NSS.
319         size.value() > UINT32_MAX || size.value() > mTotal) {
320       return NS_ERROR_OUT_OF_MEMORY;
321     }
322 
323     MOZ_DIAGNOSTIC_ASSERT(mFileData);
324     MOZ_RELEASE_ASSERT((mDataLen + aCount) <= mTotal);
325 
326     nsresult rv = mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
327     if (NS_WARN_IF(NS_FAILED(rv))) {
328       return rv;
329     }
330   }
331 
332   mDataLen += bytesRead;
333   return NS_OK;
334 }
335 
336 // Helper methods
337 
ReadFileContent(Blob & aBlob,const nsAString & aCharset,eDataFormat aDataFormat,ErrorResult & aRv)338 void FileReader::ReadFileContent(Blob& aBlob, const nsAString& aCharset,
339                                  eDataFormat aDataFormat, ErrorResult& aRv) {
340   if (IsCurrentThreadRunningWorker() && !mWeakWorkerRef) {
341     // The worker is already shutting down.
342     return;
343   }
344 
345   if (mReadyState == LOADING) {
346     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
347     return;
348   }
349 
350   mError = nullptr;
351 
352   SetDOMStringToNull(mResult);
353   mResultArrayBuffer = nullptr;
354 
355   mAsyncStream = nullptr;
356 
357   mTransferred = 0;
358   mTotal = 0;
359   mReadyState = EMPTY;
360   FreeFileData();
361 
362   mBlob = &aBlob;
363   mDataFormat = aDataFormat;
364   CopyUTF16toUTF8(aCharset, mCharset);
365 
366   {
367     nsCOMPtr<nsIInputStream> stream;
368     mBlob->CreateInputStream(getter_AddRefs(stream), aRv);
369     if (NS_WARN_IF(aRv.Failed())) {
370       return;
371     }
372 
373     aRv = NS_MakeAsyncNonBlockingInputStream(stream.forget(),
374                                              getter_AddRefs(mAsyncStream));
375     if (NS_WARN_IF(aRv.Failed())) {
376       return;
377     }
378   }
379 
380   MOZ_ASSERT(mAsyncStream);
381 
382   mTotal = mBlob->GetSize(aRv);
383   if (NS_WARN_IF(aRv.Failed())) {
384     return;
385   }
386 
387   // Binary Format doesn't need a post-processing of the data. Everything is
388   // written directly into mResult.
389   if (mDataFormat != FILE_AS_BINARY) {
390     if (mDataFormat == FILE_AS_ARRAYBUFFER) {
391       mFileData = js_pod_malloc<char>(mTotal);
392     } else {
393       mFileData = (char*)malloc(mTotal);
394     }
395 
396     if (!mFileData) {
397       NS_WARNING("Preallocation failed for ReadFileData");
398       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
399       return;
400     }
401   }
402 
403   aRv = DoAsyncWait();
404   if (NS_WARN_IF(aRv.Failed())) {
405     FreeFileData();
406     return;
407   }
408 
409   // FileReader should be in loading state here
410   mReadyState = LOADING;
411   DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
412 }
413 
GetAsText(Blob * aBlob,const nsACString & aCharset,const char * aFileData,uint32_t aDataLen,nsAString & aResult)414 nsresult FileReader::GetAsText(Blob* aBlob, const nsACString& aCharset,
415                                const char* aFileData, uint32_t aDataLen,
416                                nsAString& aResult) {
417   // Try the API argument.
418   const Encoding* encoding = Encoding::ForLabel(aCharset);
419   if (!encoding) {
420     // API argument failed. Try the type property of the blob.
421     nsAutoString type16;
422     aBlob->GetType(type16);
423     NS_ConvertUTF16toUTF8 type(type16);
424     nsAutoCString specifiedCharset;
425     bool haveCharset;
426     int32_t charsetStart, charsetEnd;
427     NS_ExtractCharsetFromContentType(type, specifiedCharset, &haveCharset,
428                                      &charsetStart, &charsetEnd);
429     encoding = Encoding::ForLabel(specifiedCharset);
430     if (!encoding) {
431       // Type property failed. Use UTF-8.
432       encoding = UTF_8_ENCODING;
433     }
434   }
435 
436   auto data = MakeSpan(reinterpret_cast<const uint8_t*>(aFileData), aDataLen);
437   nsresult rv;
438   Tie(rv, encoding) = encoding->Decode(data, aResult);
439   return NS_FAILED(rv) ? rv : NS_OK;
440 }
441 
GetAsDataURL(Blob * aBlob,const char * aFileData,uint32_t aDataLen,nsAString & aResult)442 nsresult FileReader::GetAsDataURL(Blob* aBlob, const char* aFileData,
443                                   uint32_t aDataLen, nsAString& aResult) {
444   aResult.AssignLiteral("data:");
445 
446   nsAutoString contentType;
447   aBlob->GetType(contentType);
448   if (!contentType.IsEmpty()) {
449     aResult.Append(contentType);
450   } else {
451     aResult.AppendLiteral("application/octet-stream");
452   }
453   aResult.AppendLiteral(";base64,");
454 
455   nsCString encodedData;
456   nsresult rv = Base64Encode(Substring(aFileData, aDataLen), encodedData);
457   NS_ENSURE_SUCCESS(rv, rv);
458 
459   if (!AppendASCIItoUTF16(encodedData, aResult, fallible)) {
460     return NS_ERROR_OUT_OF_MEMORY;
461   }
462 
463   return NS_OK;
464 }
465 
466 /* virtual */
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)467 JSObject* FileReader::WrapObject(JSContext* aCx,
468                                  JS::Handle<JSObject*> aGivenProto) {
469   return FileReader_Binding::Wrap(aCx, this, aGivenProto);
470 }
471 
StartProgressEventTimer()472 void FileReader::StartProgressEventTimer() {
473   if (!mProgressNotifier) {
474     mProgressNotifier = NS_NewTimer(mTarget);
475   }
476 
477   if (mProgressNotifier) {
478     mProgressEventWasDelayed = false;
479     mTimerIsActive = true;
480     mProgressNotifier->Cancel();
481     mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
482                                         nsITimer::TYPE_ONE_SHOT);
483   }
484 }
485 
ClearProgressEventTimer()486 void FileReader::ClearProgressEventTimer() {
487   mProgressEventWasDelayed = false;
488   mTimerIsActive = false;
489   if (mProgressNotifier) {
490     mProgressNotifier->Cancel();
491   }
492 }
493 
FreeFileData()494 void FileReader::FreeFileData() {
495   if (mFileData) {
496     if (mDataFormat == FILE_AS_ARRAYBUFFER) {
497       js_free(mFileData);
498     } else {
499       free(mFileData);
500     }
501     mFileData = nullptr;
502   }
503 
504   mDataLen = 0;
505 }
506 
FreeDataAndDispatchSuccess()507 void FileReader::FreeDataAndDispatchSuccess() {
508   FreeFileData();
509   mResult.SetIsVoid(false);
510   mAsyncStream = nullptr;
511   mBlob = nullptr;
512 
513   // Dispatch event to signify end of a successful operation
514   DispatchProgressEvent(NS_LITERAL_STRING(LOAD_STR));
515   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
516 }
517 
FreeDataAndDispatchError()518 void FileReader::FreeDataAndDispatchError() {
519   MOZ_ASSERT(mError);
520 
521   FreeFileData();
522   mResult.SetIsVoid(true);
523   mAsyncStream = nullptr;
524   mBlob = nullptr;
525 
526   // Dispatch error event to signify load failure
527   DispatchProgressEvent(NS_LITERAL_STRING(ERROR_STR));
528   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
529 }
530 
FreeDataAndDispatchError(nsresult aRv)531 void FileReader::FreeDataAndDispatchError(nsresult aRv) {
532   // Set the status attribute, and dispatch the error event
533   switch (aRv) {
534     case NS_ERROR_FILE_NOT_FOUND:
535       mError = DOMException::Create(NS_ERROR_DOM_NOT_FOUND_ERR);
536       break;
537     case NS_ERROR_FILE_ACCESS_DENIED:
538       mError = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
539       break;
540     default:
541       mError = DOMException::Create(NS_ERROR_DOM_FILE_NOT_READABLE_ERR);
542       break;
543   }
544 
545   FreeDataAndDispatchError();
546 }
547 
DispatchProgressEvent(const nsAString & aType)548 nsresult FileReader::DispatchProgressEvent(const nsAString& aType) {
549   ProgressEventInit init;
550   init.mBubbles = false;
551   init.mCancelable = false;
552   init.mLoaded = mTransferred;
553 
554   if (mTotal != kUnknownSize) {
555     init.mLengthComputable = true;
556     init.mTotal = mTotal;
557   } else {
558     init.mLengthComputable = false;
559     init.mTotal = 0;
560   }
561   RefPtr<ProgressEvent> event = ProgressEvent::Constructor(this, aType, init);
562   event->SetTrusted(true);
563 
564   ErrorResult rv;
565   DispatchEvent(*event, rv);
566   return rv.StealNSResult();
567 }
568 
569 // nsITimerCallback
570 NS_IMETHODIMP
Notify(nsITimer * aTimer)571 FileReader::Notify(nsITimer* aTimer) {
572   nsresult rv;
573   mTimerIsActive = false;
574 
575   if (mProgressEventWasDelayed) {
576     rv = DispatchProgressEvent(NS_LITERAL_STRING("progress"));
577     NS_ENSURE_SUCCESS(rv, rv);
578 
579     StartProgressEventTimer();
580   }
581 
582   return NS_OK;
583 }
584 
585 // InputStreamCallback
586 NS_IMETHODIMP
OnInputStreamReady(nsIAsyncInputStream * aStream)587 FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream) {
588   if (mReadyState != LOADING || aStream != mAsyncStream) {
589     return NS_OK;
590   }
591 
592   // We use this class to decrease the busy counter at the end of this method.
593   // In theory we can do it immediatelly but, for debugging reasons, we want to
594   // be 100% sure we have a workerRef when OnLoadEnd() is called.
595   FileReaderDecreaseBusyCounter RAII(this);
596 
597   uint64_t count;
598   nsresult rv = aStream->Available(&count);
599 
600   if (NS_SUCCEEDED(rv) && count) {
601     rv = DoReadData(count);
602 
603     if (NS_SUCCEEDED(rv)) {
604       rv = DoAsyncWait();
605     }
606   }
607 
608   if (NS_FAILED(rv) || !count) {
609     if (rv == NS_BASE_STREAM_CLOSED) {
610       rv = NS_OK;
611     }
612     OnLoadEnd(rv);
613     return NS_OK;
614   }
615 
616   mTransferred += count;
617 
618   // Notify the timer is the appropriate timeframe has passed
619   if (mTimerIsActive) {
620     mProgressEventWasDelayed = true;
621   } else {
622     rv = DispatchProgressEvent(NS_LITERAL_STRING(PROGRESS_STR));
623     NS_ENSURE_SUCCESS(rv, rv);
624 
625     StartProgressEventTimer();
626   }
627 
628   return NS_OK;
629 }
630 
631 // nsINamed
632 NS_IMETHODIMP
GetName(nsACString & aName)633 FileReader::GetName(nsACString& aName) {
634   aName.AssignLiteral("FileReader");
635   return NS_OK;
636 }
637 
OnLoadEnd(nsresult aStatus)638 void FileReader::OnLoadEnd(nsresult aStatus) {
639   // Cancel the progress event timer
640   ClearProgressEventTimer();
641 
642   // FileReader must be in DONE stage after an operation
643   mReadyState = DONE;
644 
645   // Quick return, if failed.
646   if (NS_FAILED(aStatus)) {
647     FreeDataAndDispatchError(aStatus);
648     return;
649   }
650 
651   // In case we read a different number of bytes, we can assume that the
652   // underlying storage has changed. We should not continue.
653   if (mDataLen != mTotal) {
654     FreeDataAndDispatchError(NS_ERROR_FAILURE);
655     return;
656   }
657 
658   // ArrayBuffer needs a custom handling.
659   if (mDataFormat == FILE_AS_ARRAYBUFFER) {
660     OnLoadEndArrayBuffer();
661     return;
662   }
663 
664   nsresult rv = NS_OK;
665 
666   // We don't do anything special for Binary format.
667 
668   if (mDataFormat == FILE_AS_DATAURL) {
669     rv = GetAsDataURL(mBlob, mFileData, mDataLen, mResult);
670   } else if (mDataFormat == FILE_AS_TEXT) {
671     if (!mFileData && mDataLen) {
672       rv = NS_ERROR_OUT_OF_MEMORY;
673     } else if (!mFileData) {
674       rv = GetAsText(mBlob, mCharset, "", mDataLen, mResult);
675     } else {
676       rv = GetAsText(mBlob, mCharset, mFileData, mDataLen, mResult);
677     }
678   }
679 
680   if (NS_WARN_IF(NS_FAILED(rv))) {
681     FreeDataAndDispatchError(rv);
682     return;
683   }
684 
685   FreeDataAndDispatchSuccess();
686 }
687 
Abort()688 void FileReader::Abort() {
689   if (mReadyState == EMPTY || mReadyState == DONE) {
690     return;
691   }
692 
693   MOZ_ASSERT(mReadyState == LOADING);
694 
695   ClearProgressEventTimer();
696 
697   mReadyState = DONE;
698 
699   // XXX The spec doesn't say this
700   mError = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
701 
702   // Revert status and result attributes
703   SetDOMStringToNull(mResult);
704   mResultArrayBuffer = nullptr;
705 
706   mAsyncStream = nullptr;
707   mBlob = nullptr;
708 
709   // Clean up memory buffer
710   FreeFileData();
711 
712   // Dispatch the events
713   DispatchProgressEvent(NS_LITERAL_STRING(ABORT_STR));
714   DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
715 }
716 
IncreaseBusyCounter()717 nsresult FileReader::IncreaseBusyCounter() {
718   if (mWeakWorkerRef && mBusyCount++ == 0) {
719     if (NS_WARN_IF(!mWeakWorkerRef->GetPrivate())) {
720       return NS_ERROR_FAILURE;
721     }
722 
723     RefPtr<FileReader> self = this;
724 
725     RefPtr<StrongWorkerRef> ref =
726         StrongWorkerRef::Create(mWeakWorkerRef->GetPrivate(), "FileReader",
727                                 [self]() { self->Shutdown(); });
728     if (NS_WARN_IF(!ref)) {
729       return NS_ERROR_FAILURE;
730     }
731 
732     mStrongWorkerRef = ref;
733   }
734 
735   return NS_OK;
736 }
737 
DecreaseBusyCounter()738 void FileReader::DecreaseBusyCounter() {
739   MOZ_ASSERT_IF(mStrongWorkerRef, mBusyCount);
740   if (mStrongWorkerRef && --mBusyCount == 0) {
741     mStrongWorkerRef = nullptr;
742   }
743 }
744 
Shutdown()745 void FileReader::Shutdown() {
746   mReadyState = DONE;
747 
748   if (mAsyncStream) {
749     mAsyncStream->Close();
750     mAsyncStream = nullptr;
751   }
752 
753   FreeFileData();
754   mResultArrayBuffer = nullptr;
755 
756   if (mWeakWorkerRef && mBusyCount != 0) {
757     mStrongWorkerRef = nullptr;
758     mWeakWorkerRef = nullptr;
759     mBusyCount = 0;
760   }
761 }
762 
763 }  // namespace dom
764 }  // namespace mozilla
765