/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 |