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