Coverage Report

Created: 2018-09-25 14:53

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