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 already_AddRefed
<FileReaderSync
> FileReaderSync::Constructor(
40 const GlobalObject
& aGlobal
) {
41 RefPtr
<FileReaderSync
> frs
= new FileReaderSync();
46 bool FileReaderSync::WrapObject(JSContext
* aCx
,
47 JS::Handle
<JSObject
*> aGivenProto
,
48 JS::MutableHandle
<JSObject
*> aReflector
) {
49 return FileReaderSync_Binding::Wrap(aCx
, this, aGivenProto
, aReflector
);
52 void FileReaderSync::ReadAsArrayBuffer(JSContext
* aCx
,
53 JS::Handle
<JSObject
*> aScopeObj
,
55 JS::MutableHandle
<JSObject
*> aRetval
,
57 uint64_t blobSize
= aBlob
.GetSize(aRv
);
58 if (NS_WARN_IF(aRv
.Failed())) {
62 UniquePtr
<char[], JS::FreePolicy
> bufferData(
63 js_pod_arena_malloc
<char>(js::ArrayBufferContentsArena
, blobSize
));
65 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
69 nsCOMPtr
<nsIInputStream
> stream
;
70 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
71 if (NS_WARN_IF(aRv
.Failed())) {
76 aRv
= SyncRead(stream
, bufferData
.get(), blobSize
, &numRead
);
77 if (NS_WARN_IF(aRv
.Failed())) {
81 // The file is changed in the meantime?
82 if (numRead
!= blobSize
) {
83 aRv
.Throw(NS_ERROR_FAILURE
);
87 JSObject
* arrayBuffer
=
88 JS::NewArrayBufferWithContents(aCx
, blobSize
, std::move(bufferData
));
90 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
94 aRetval
.set(arrayBuffer
);
97 void FileReaderSync::ReadAsBinaryString(Blob
& aBlob
, nsAString
& aResult
,
99 nsCOMPtr
<nsIInputStream
> stream
;
100 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
101 if (NS_WARN_IF(aRv
.Failed())) {
108 aRv
= SyncRead(stream
, readBuf
, sizeof(readBuf
), &numRead
);
109 if (NS_WARN_IF(aRv
.Failed())) {
113 uint32_t oldLength
= aResult
.Length();
114 AppendASCIItoUTF16(Substring(readBuf
, readBuf
+ numRead
), aResult
);
115 if (aResult
.Length() - oldLength
!= numRead
) {
116 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
119 } while (numRead
> 0);
122 void FileReaderSync::ReadAsText(Blob
& aBlob
,
123 const Optional
<nsAString
>& aEncoding
,
124 nsAString
& aResult
, ErrorResult
& aRv
) {
125 nsCOMPtr
<nsIInputStream
> stream
;
126 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
127 if (NS_WARN_IF(aRv
.Failed())) {
132 if (!sniffBuf
.SetLength(3, fallible
)) {
133 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
137 uint32_t numRead
= 0;
138 aRv
= SyncRead(stream
, sniffBuf
.BeginWriting(), sniffBuf
.Length(), &numRead
);
139 if (NS_WARN_IF(aRv
.Failed())) {
143 // No data, we don't need to continue.
149 // Try the API argument.
150 const Encoding
* encoding
=
151 aEncoding
.WasPassed() ? Encoding::ForLabel(aEncoding
.Value()) : nullptr;
153 // API argument failed. Try the type property of the blob.
155 aBlob
.GetType(type16
);
156 NS_ConvertUTF16toUTF8
type(type16
);
157 nsAutoCString specifiedCharset
;
159 int32_t charsetStart
, charsetEnd
;
160 NS_ExtractCharsetFromContentType(type
, specifiedCharset
, &haveCharset
,
161 &charsetStart
, &charsetEnd
);
162 encoding
= Encoding::ForLabel(specifiedCharset
);
164 // Type property failed. Use UTF-8.
165 encoding
= UTF_8_ENCODING
;
169 if (numRead
< sniffBuf
.Length()) {
170 sniffBuf
.Truncate(numRead
);
173 // Let's recreate the full stream using a:
174 // multiplexStream(syncStream + original stream)
175 // In theory, we could try to see if the inputStream is a nsISeekableStream,
176 // but this doesn't work correctly for nsPipe3 - See bug 1349570.
178 nsCOMPtr
<nsIMultiplexInputStream
> multiplexStream
=
179 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
180 if (NS_WARN_IF(!multiplexStream
)) {
181 aRv
.Throw(NS_ERROR_FAILURE
);
185 nsCOMPtr
<nsIInputStream
> sniffStringStream
;
186 aRv
= NS_NewCStringInputStream(getter_AddRefs(sniffStringStream
), sniffBuf
);
187 if (NS_WARN_IF(aRv
.Failed())) {
191 aRv
= multiplexStream
->AppendStream(sniffStringStream
);
192 if (NS_WARN_IF(aRv
.Failed())) {
196 uint64_t blobSize
= aBlob
.GetSize(aRv
);
197 if (NS_WARN_IF(aRv
.Failed())) {
201 nsCOMPtr
<nsIInputStream
> syncStream
;
202 aRv
= ConvertAsyncToSyncStream(blobSize
- sniffBuf
.Length(), stream
.forget(),
203 getter_AddRefs(syncStream
));
204 if (NS_WARN_IF(aRv
.Failed())) {
208 // ConvertAsyncToSyncStream returns a null syncStream if the stream has been
209 // already closed or there is nothing to read.
211 aRv
= multiplexStream
->AppendStream(syncStream
);
212 if (NS_WARN_IF(aRv
.Failed())) {
217 nsAutoCString charset
;
218 encoding
->Name(charset
);
220 nsCOMPtr
<nsIInputStream
> multiplex(do_QueryInterface(multiplexStream
));
221 aRv
= ConvertStream(multiplex
, charset
.get(), aResult
);
222 if (NS_WARN_IF(aRv
.Failed())) {
227 void FileReaderSync::ReadAsDataURL(Blob
& aBlob
, nsAString
& aResult
,
229 nsAutoString scratchResult
;
230 scratchResult
.AssignLiteral("data:");
232 nsString contentType
;
233 aBlob
.GetType(contentType
);
235 if (contentType
.IsEmpty()) {
236 scratchResult
.AppendLiteral("application/octet-stream");
238 scratchResult
.Append(contentType
);
240 scratchResult
.AppendLiteral(";base64,");
242 nsCOMPtr
<nsIInputStream
> stream
;
243 aBlob
.CreateInputStream(getter_AddRefs(stream
), aRv
);
244 if (NS_WARN_IF(aRv
.Failed())) {
248 uint64_t blobSize
= aBlob
.GetSize(aRv
);
249 if (NS_WARN_IF(aRv
.Failed())) {
253 nsCOMPtr
<nsIInputStream
> syncStream
;
254 aRv
= ConvertAsyncToSyncStream(blobSize
, stream
.forget(),
255 getter_AddRefs(syncStream
));
256 if (NS_WARN_IF(aRv
.Failed())) {
260 MOZ_ASSERT(syncStream
);
263 aRv
= syncStream
->Available(&size
);
264 if (NS_WARN_IF(aRv
.Failed())) {
268 // The file is changed in the meantime?
269 if (blobSize
!= size
) {
273 nsAutoString encodedData
;
274 aRv
= Base64EncodeInputStream(syncStream
, encodedData
, size
);
275 if (NS_WARN_IF(aRv
.Failed())) {
279 scratchResult
.Append(encodedData
);
281 aResult
= scratchResult
;
284 nsresult
FileReaderSync::ConvertStream(nsIInputStream
* aStream
,
285 const char* aCharset
,
286 nsAString
& aResult
) {
287 nsCOMPtr
<nsIConverterInputStream
> converterStream
=
288 do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
289 NS_ENSURE_TRUE(converterStream
, NS_ERROR_FAILURE
);
291 nsresult rv
= converterStream
->Init(
292 aStream
, aCharset
, 8192,
293 nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER
);
294 NS_ENSURE_SUCCESS(rv
, rv
);
296 nsCOMPtr
<nsIUnicharInputStream
> unicharStream
= converterStream
;
297 NS_ENSURE_TRUE(unicharStream
, NS_ERROR_FAILURE
);
301 while (NS_SUCCEEDED(unicharStream
->ReadString(8192, result
, &numChars
)) &&
303 uint32_t oldLength
= aResult
.Length();
304 aResult
.Append(result
);
305 if (aResult
.Length() - oldLength
!= result
.Length()) {
306 return NS_ERROR_OUT_OF_MEMORY
;
315 // This runnable is used to terminate the sync event loop.
316 class ReadReadyRunnable final
: public WorkerSyncRunnable
{
318 ReadReadyRunnable(WorkerPrivate
* aWorkerPrivate
,
319 nsIEventTarget
* aSyncLoopTarget
)
320 : WorkerSyncRunnable(aWorkerPrivate
, aSyncLoopTarget
) {}
322 bool WorkerRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
) override
{
323 aWorkerPrivate
->AssertIsOnWorkerThread();
324 MOZ_ASSERT(mSyncLoopTarget
);
326 nsCOMPtr
<nsIEventTarget
> syncLoopTarget
;
327 mSyncLoopTarget
.swap(syncLoopTarget
);
329 aWorkerPrivate
->StopSyncLoop(syncLoopTarget
, NS_OK
);
334 ~ReadReadyRunnable() override
= default;
337 // This class implements nsIInputStreamCallback and it will be called when the
338 // stream is ready to be read.
339 class ReadCallback final
: public nsIInputStreamCallback
{
341 NS_DECL_THREADSAFE_ISUPPORTS
343 ReadCallback(WorkerPrivate
* aWorkerPrivate
, nsIEventTarget
* aEventTarget
)
344 : mWorkerPrivate(aWorkerPrivate
), mEventTarget(aEventTarget
) {}
347 OnInputStreamReady(nsIAsyncInputStream
* aStream
) override
{
348 // I/O Thread. Now we need to block the sync event loop.
349 RefPtr
<ReadReadyRunnable
> runnable
=
350 new ReadReadyRunnable(mWorkerPrivate
, mEventTarget
);
351 return mEventTarget
->Dispatch(runnable
.forget(), NS_DISPATCH_NORMAL
);
355 ~ReadCallback() = default;
357 // The worker is kept alive because of the sync event loop.
358 WorkerPrivate
* mWorkerPrivate
;
359 nsCOMPtr
<nsIEventTarget
> mEventTarget
;
362 NS_IMPL_ADDREF(ReadCallback
);
363 NS_IMPL_RELEASE(ReadCallback
);
365 NS_INTERFACE_MAP_BEGIN(ReadCallback
)
366 NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback
)
367 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIInputStreamCallback
)
372 nsresult
FileReaderSync::SyncRead(nsIInputStream
* aStream
, char* aBuffer
,
373 uint32_t aBufferSize
,
374 uint32_t* aTotalBytesRead
) {
377 MOZ_ASSERT(aTotalBytesRead
);
379 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
380 MOZ_ASSERT(workerPrivate
);
382 *aTotalBytesRead
= 0;
384 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
;
385 nsCOMPtr
<nsIEventTarget
> target
;
387 while (*aTotalBytesRead
< aBufferSize
) {
388 uint32_t currentBytesRead
= 0;
390 // Let's read something.
392 aStream
->Read(aBuffer
+ *aTotalBytesRead
,
393 aBufferSize
- *aTotalBytesRead
, ¤tBytesRead
);
395 // Nothing else to read.
396 if (rv
== NS_BASE_STREAM_CLOSED
||
397 (NS_SUCCEEDED(rv
) && currentBytesRead
== 0)) {
402 if (NS_FAILED(rv
) && rv
!= NS_BASE_STREAM_WOULD_BLOCK
) {
407 if (NS_SUCCEEDED(rv
)) {
408 *aTotalBytesRead
+= currentBytesRead
;
412 // We need to proceed async.
414 asyncStream
= do_QueryInterface(aStream
);
420 AutoSyncLoopHolder
syncLoop(workerPrivate
, Canceling
);
422 nsCOMPtr
<nsISerialEventTarget
> syncLoopTarget
=
423 syncLoop
.GetSerialEventTarget();
424 if (!syncLoopTarget
) {
425 // SyncLoop creation can fail if the worker is shutting down.
426 return NS_ERROR_DOM_INVALID_STATE_ERR
;
429 RefPtr
<ReadCallback
> callback
=
430 new ReadCallback(workerPrivate
, syncLoopTarget
);
433 target
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID
);
437 rv
= asyncStream
->AsyncWait(callback
, 0, aBufferSize
- *aTotalBytesRead
,
439 if (NS_WARN_IF(NS_FAILED(rv
))) {
443 if (NS_WARN_IF(NS_FAILED(syncLoop
.Run()))) {
444 return NS_ERROR_DOM_INVALID_STATE_ERR
;
451 nsresult
FileReaderSync::ConvertAsyncToSyncStream(
452 uint64_t aStreamSize
, already_AddRefed
<nsIInputStream
> aAsyncStream
,
453 nsIInputStream
** aSyncStream
) {
454 nsCOMPtr
<nsIInputStream
> asyncInputStream
= std::move(aAsyncStream
);
456 // If the stream is not async, we just need it to be bufferable.
457 nsCOMPtr
<nsIAsyncInputStream
> asyncStream
=
458 do_QueryInterface(asyncInputStream
);
460 return NS_NewBufferedInputStream(aSyncStream
, asyncInputStream
.forget(),
464 nsAutoCString buffer
;
465 if (!buffer
.SetLength(aStreamSize
, fallible
)) {
466 return NS_ERROR_OUT_OF_MEMORY
;
471 SyncRead(asyncInputStream
, buffer
.BeginWriting(), aStreamSize
, &read
);
472 if (NS_WARN_IF(NS_FAILED(rv
))) {
476 if (read
!= aStreamSize
) {
477 return NS_ERROR_FAILURE
;
480 rv
= NS_NewCStringInputStream(aSyncStream
, std::move(buffer
));
481 if (NS_WARN_IF(NS_FAILED(rv
))) {