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