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/. */
7 #include "FileReaderSync.h"
9 #include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
10 #include "js/RootingAPI.h" // JS::{,Mutable}Handle
11 #include "js/Utility.h" // js::ArrayBufferContentsArena, JS::FreePolicy, js_pod_arena_malloc
12 #include "mozilla/Unused.h"
13 #include "mozilla/Base64.h"
14 #include "mozilla/dom/File.h"
15 #include "mozilla/Encoding.h"
16 #include "mozilla/dom/FileReaderSyncBinding.h"
17 #include "nsCExternalHandlerService.h"
18 #include "nsComponentManagerUtils.h"
21 #include "nsIConverterInputStream.h"
22 #include "nsIInputStream.h"
23 #include "nsIMultiplexInputStream.h"
24 #include "nsStreamUtils.h"
25 #include "nsStringStream.h"
26 #include "nsISupportsImpl.h"
27 #include "nsNetUtil.h"
28 #include "nsServiceManagerUtils.h"
29 #include "nsIAsyncInputStream.h"
30 #include "mozilla/dom/WorkerPrivate.h"
31 #include "mozilla/dom/WorkerRunnable.h"
33 using namespace mozilla
;
34 using namespace mozilla::dom
;
35 using mozilla::dom::GlobalObject
;
36 using mozilla::dom::Optional
;
39 UniquePtr
<FileReaderSync
> FileReaderSync::Constructor(
40 const GlobalObject
& aGlobal
) {
41 return MakeUnique
<FileReaderSync
>();
44 bool FileReaderSync::WrapObject(JSContext
* aCx
,
45 JS::Handle
<JSObject
*> aGivenProto
,
46 JS::MutableHandle
<JSObject
*> aReflector
) {
47 return FileReaderSync_Binding::Wrap(aCx
, this, aGivenProto
, aReflector
);
50 void FileReaderSync::ReadAsArrayBuffer(JSContext
* aCx
,
51 JS::Handle
<JSObject
*> aScopeObj
,
53 JS::MutableHandle
<JSObject
*> aRetval
,
55 uint64_t blobSize
= aBlob
.GetSize(aRv
);
56 if (NS_WARN_IF(aRv
.Failed())) {
60 UniquePtr
<char[], JS::FreePolicy
> bufferData(
61 js_pod_arena_malloc
<char>(js::ArrayBufferContentsArena
, blobSize
));
63 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
67 nsCOMPtr
<nsIInputStream
> stream
;
68 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
69 if (NS_WARN_IF(aRv
.Failed())) {
74 aRv
= SyncRead(stream
, bufferData
.get(), blobSize
, &numRead
);
75 if (NS_WARN_IF(aRv
.Failed())) {
79 // The file is changed in the meantime?
80 if (numRead
!= blobSize
) {
81 aRv
.Throw(NS_ERROR_FAILURE
);
85 JSObject
* arrayBuffer
=
86 JS::NewArrayBufferWithContents(aCx
, blobSize
, std::move(bufferData
));
88 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
92 aRetval
.set(arrayBuffer
);
95 void FileReaderSync::ReadAsBinaryString(Blob
& aBlob
, nsAString
& aResult
,
97 nsCOMPtr
<nsIInputStream
> stream
;
98 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
99 if (NS_WARN_IF(aRv
.Failed())) {
106 aRv
= SyncRead(stream
, readBuf
, sizeof(readBuf
), &numRead
);
107 if (NS_WARN_IF(aRv
.Failed())) {
111 uint32_t oldLength
= aResult
.Length();
112 AppendASCIItoUTF16(Substring(readBuf
, readBuf
+ numRead
), aResult
);
113 if (aResult
.Length() - oldLength
!= numRead
) {
114 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
117 } while (numRead
> 0);
120 void FileReaderSync::ReadAsText(Blob
& aBlob
,
121 const Optional
<nsAString
>& aEncoding
,
122 nsAString
& aResult
, ErrorResult
& aRv
) {
123 nsCOMPtr
<nsIInputStream
> stream
;
124 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
125 if (NS_WARN_IF(aRv
.Failed())) {
130 if (!sniffBuf
.SetLength(3, fallible
)) {
131 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
135 uint32_t numRead
= 0;
136 aRv
= SyncRead(stream
, sniffBuf
.BeginWriting(), sniffBuf
.Length(), &numRead
);
137 if (NS_WARN_IF(aRv
.Failed())) {
141 // No data, we don't need to continue.
147 // Try the API argument.
148 const Encoding
* encoding
=
149 aEncoding
.WasPassed() ? Encoding::ForLabel(aEncoding
.Value()) : nullptr;
151 // API argument failed. Try the type property of the blob.
153 aBlob
.GetType(type16
);
154 NS_ConvertUTF16toUTF8
type(type16
);
155 nsAutoCString specifiedCharset
;
157 int32_t charsetStart
, charsetEnd
;
158 NS_ExtractCharsetFromContentType(type
, specifiedCharset
, &haveCharset
,
159 &charsetStart
, &charsetEnd
);
160 encoding
= Encoding::ForLabel(specifiedCharset
);
162 // Type property failed. Use UTF-8.
163 encoding
= UTF_8_ENCODING
;
167 if (numRead
< sniffBuf
.Length()) {
168 sniffBuf
.Truncate(numRead
);
171 // Let's recreate the full stream using a:
172 // multiplexStream(syncStream + original stream)
173 // In theory, we could try to see if the inputStream is a nsISeekableStream,
174 // but this doesn't work correctly for nsPipe3 - See bug 1349570.
176 nsCOMPtr
<nsIMultiplexInputStream
> multiplexStream
=
177 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
178 if (NS_WARN_IF(!multiplexStream
)) {
179 aRv
.Throw(NS_ERROR_FAILURE
);
183 nsCOMPtr
<nsIInputStream
> sniffStringStream
;
184 aRv
= NS_NewCStringInputStream(getter_AddRefs(sniffStringStream
), sniffBuf
);
185 if (NS_WARN_IF(aRv
.Failed())) {
189 aRv
= multiplexStream
->AppendStream(sniffStringStream
);
190 if (NS_WARN_IF(aRv
.Failed())) {
194 uint64_t blobSize
= aBlob
.GetSize(aRv
);
195 if (NS_WARN_IF(aRv
.Failed())) {
199 nsCOMPtr
<nsIInputStream
> syncStream
;
200 aRv
= ConvertAsyncToSyncStream(blobSize
- sniffBuf
.Length(), stream
.forget(),
201 getter_AddRefs(syncStream
));
202 if (NS_WARN_IF(aRv
.Failed())) {
206 // ConvertAsyncToSyncStream returns a null syncStream if the stream has been
207 // already closed or there is nothing to read.
209 aRv
= multiplexStream
->AppendStream(syncStream
);
210 if (NS_WARN_IF(aRv
.Failed())) {
215 nsAutoCString charset
;
216 encoding
->Name(charset
);
218 nsCOMPtr
<nsIInputStream
> multiplex(do_QueryInterface(multiplexStream
));
219 aRv
= ConvertStream(multiplex
, charset
.get(), aResult
);
220 if (NS_WARN_IF(aRv
.Failed())) {
225 void FileReaderSync::ReadAsDataURL(Blob
& aBlob
, nsAString
& aResult
,
227 nsAutoString scratchResult
;
228 scratchResult
.AssignLiteral("data:");
230 nsString contentType
;
231 aBlob
.GetType(contentType
);
233 if (contentType
.IsEmpty()) {
234 scratchResult
.AppendLiteral("application/octet-stream");
236 scratchResult
.Append(contentType
);
238 scratchResult
.AppendLiteral(";base64,");
240 nsCOMPtr
<nsIInputStream
> stream
;
241 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
242 if (NS_WARN_IF(aRv
.Failed())) {
246 uint64_t blobSize
= aBlob
.GetSize(aRv
);
247 if (NS_WARN_IF(aRv
.Failed())) {
251 nsCOMPtr
<nsIInputStream
> syncStream
;
252 aRv
= ConvertAsyncToSyncStream(blobSize
, stream
.forget(),
253 getter_AddRefs(syncStream
));
254 if (NS_WARN_IF(aRv
.Failed())) {
258 MOZ_ASSERT(syncStream
);
261 aRv
= syncStream
->Available(&size
);
262 if (NS_WARN_IF(aRv
.Failed())) {
266 // The file is changed in the meantime?
267 if (blobSize
!= size
) {
271 nsAutoString encodedData
;
272 aRv
= Base64EncodeInputStream(syncStream
, encodedData
, size
);
273 if (NS_WARN_IF(aRv
.Failed())) {
277 scratchResult
.Append(encodedData
);
279 aResult
= scratchResult
;
282 nsresult
FileReaderSync::ConvertStream(nsIInputStream
* aStream
,
283 const char* aCharset
,
284 nsAString
& aResult
) {
285 nsCOMPtr
<nsIConverterInputStream
> converterStream
=
286 do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
287 NS_ENSURE_TRUE(converterStream
, NS_ERROR_FAILURE
);
289 nsresult rv
= converterStream
->Init(
290 aStream
, aCharset
, 8192,
291 nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER
);
292 NS_ENSURE_SUCCESS(rv
, rv
);
294 nsCOMPtr
<nsIUnicharInputStream
> unicharStream
= converterStream
;
295 NS_ENSURE_TRUE(unicharStream
, NS_ERROR_FAILURE
);
299 while (NS_SUCCEEDED(unicharStream
->ReadString(8192, result
, &numChars
)) &&
301 uint32_t oldLength
= aResult
.Length();
302 aResult
.Append(result
);
303 if (aResult
.Length() - oldLength
!= result
.Length()) {
304 return NS_ERROR_OUT_OF_MEMORY
;
313 // This runnable is used to terminate the sync event loop.
314 class ReadReadyRunnable final
: public WorkerSyncRunnable
{
316 ReadReadyRunnable(WorkerPrivate
* aWorkerPrivate
,
317 nsIEventTarget
* aSyncLoopTarget
)
318 : WorkerSyncRunnable(aWorkerPrivate
, aSyncLoopTarget
,
319 "ReadReadyRunnable") {}
321 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
322 aWorkerPrivate
->AssertIsOnWorkerThread();
323 MOZ_ASSERT(mSyncLoopTarget
);
325 nsCOMPtr
<nsIEventTarget
> syncLoopTarget
;
326 mSyncLoopTarget
.swap(syncLoopTarget
);
328 aWorkerPrivate
->StopSyncLoop(syncLoopTarget
, NS_OK
);
333 ~ReadReadyRunnable() override
= default;
336 // This class implements nsIInputStreamCallback and it will be called when the
337 // stream is ready to be read.
338 class ReadCallback final
: public nsIInputStreamCallback
{
340 NS_DECL_THREADSAFE_ISUPPORTS
342 ReadCallback(WorkerPrivate
* aWorkerPrivate
, nsIEventTarget
* aEventTarget
)
343 : mWorkerPrivate(aWorkerPrivate
), mEventTarget(aEventTarget
) {}
346 OnInputStreamReady(nsIAsyncInputStream
* aStream
) override
{
347 // I/O Thread. Now we need to block the sync event loop.
348 RefPtr
<ReadReadyRunnable
> runnable
=
349 new ReadReadyRunnable(mWorkerPrivate
, mEventTarget
);
350 return mEventTarget
->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
354 ~ReadCallback() = default;
356 // The worker is kept alive because of the sync event loop.
357 WorkerPrivate
* mWorkerPrivate
;
358 nsCOMPtr
<nsIEventTarget
> mEventTarget
;
361 NS_IMPL_ADDREF(ReadCallback
);
362 NS_IMPL_RELEASE(ReadCallback
);
364 NS_INTERFACE_MAP_BEGIN(ReadCallback
)
365 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback
)
366 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIInputStreamCallback
)
371 nsresult
FileReaderSync::SyncRead(nsIInputStream
* aStream
, char* aBuffer
,
372 uint32_t aBufferSize
,
373 uint32_t* aTotalBytesRead
) {
376 MOZ_ASSERT(aTotalBytesRead
);
378 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
379 MOZ_ASSERT(workerPrivate
);
381 *aTotalBytesRead
= 0;
383 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
;
384 nsCOMPtr
<nsIEventTarget
> target
;
386 while (*aTotalBytesRead
< aBufferSize
) {
387 uint32_t currentBytesRead
= 0;
389 // Let's read something.
391 aStream
->Read(aBuffer
+ *aTotalBytesRead
,
392 aBufferSize
- *aTotalBytesRead
, ¤tBytesRead
);
394 // Nothing else to read.
395 if (rv
== NS_BASE_STREAM_CLOSED
||
396 (NS_SUCCEEDED(rv
) && currentBytesRead
== 0)) {
401 if (NS_FAILED(rv
) && rv
!= NS_BASE_STREAM_WOULD_BLOCK
) {
406 if (NS_SUCCEEDED(rv
)) {
407 *aTotalBytesRead
+= currentBytesRead
;
411 // We need to proceed async.
413 asyncStream
= do_QueryInterface(aStream
);
419 AutoSyncLoopHolder
syncLoop(workerPrivate
, Canceling
);
421 nsCOMPtr
<nsISerialEventTarget
> syncLoopTarget
=
422 syncLoop
.GetSerialEventTarget();
423 if (!syncLoopTarget
) {
424 // SyncLoop creation can fail if the worker is shutting down.
425 return NS_ERROR_DOM_INVALID_STATE_ERR
;
428 RefPtr
<ReadCallback
> callback
=
429 new ReadCallback(workerPrivate
, syncLoopTarget
);
432 target
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
436 rv
= asyncStream
->AsyncWait(callback
, 0, aBufferSize
- *aTotalBytesRead
,
438 if (NS_WARN_IF(NS_FAILED(rv
))) {
442 if (NS_WARN_IF(NS_FAILED(syncLoop
.Run()))) {
443 return NS_ERROR_DOM_INVALID_STATE_ERR
;
450 nsresult
FileReaderSync::ConvertAsyncToSyncStream(
451 uint64_t aStreamSize
, already_AddRefed
<nsIInputStream
> aAsyncStream
,
452 nsIInputStream
** aSyncStream
) {
453 nsCOMPtr
<nsIInputStream
> asyncInputStream
= std::move(aAsyncStream
);
455 // If the stream is not async, we just need it to be bufferable.
456 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
=
457 do_QueryInterface(asyncInputStream
);
459 return NS_NewBufferedInputStream(aSyncStream
, asyncInputStream
.forget(),
463 nsAutoCString buffer
;
464 if (!buffer
.SetLength(aStreamSize
, fallible
)) {
465 return NS_ERROR_OUT_OF_MEMORY
;
470 SyncRead(asyncInputStream
, buffer
.BeginWriting(), aStreamSize
, &read
);
471 if (NS_WARN_IF(NS_FAILED(rv
))) {
475 if (read
!= aStreamSize
) {
476 return NS_ERROR_FAILURE
;
479 rv
= NS_NewCStringInputStream(aSyncStream
, std::move(buffer
));
480 if (NS_WARN_IF(NS_FAILED(rv
))) {