Bug 1854550 - pt 10. Allow LOG() with zero extra arguments r=glandium
[gecko.git] / dom / file / FileReaderSync.cpp
blob743ba21877531fd5d2218a8f7503449d0284c103
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"
19 #include "nsCOMPtr.h"
20 #include "nsError.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;
38 // static
39 already_AddRefed<FileReaderSync> FileReaderSync::Constructor(
40 const GlobalObject& aGlobal) {
41 RefPtr<FileReaderSync> frs = new FileReaderSync();
43 return frs.forget();
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,
54 Blob& aBlob,
55 JS::MutableHandle<JSObject*> aRetval,
56 ErrorResult& aRv) {
57 uint64_t blobSize = aBlob.GetSize(aRv);
58 if (NS_WARN_IF(aRv.Failed())) {
59 return;
62 UniquePtr<char[], JS::FreePolicy> bufferData(
63 js_pod_arena_malloc<char>(js::ArrayBufferContentsArena, blobSize));
64 if (!bufferData) {
65 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
66 return;
69 nsCOMPtr<nsIInputStream> stream;
70 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
71 if (NS_WARN_IF(aRv.Failed())) {
72 return;
75 uint32_t numRead;
76 aRv = SyncRead(stream, bufferData.get(), blobSize, &numRead);
77 if (NS_WARN_IF(aRv.Failed())) {
78 return;
81 // The file is changed in the meantime?
82 if (numRead != blobSize) {
83 aRv.Throw(NS_ERROR_FAILURE);
84 return;
87 JSObject* arrayBuffer =
88 JS::NewArrayBufferWithContents(aCx, blobSize, std::move(bufferData));
89 if (!arrayBuffer) {
90 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
91 return;
94 aRetval.set(arrayBuffer);
97 void FileReaderSync::ReadAsBinaryString(Blob& aBlob, nsAString& aResult,
98 ErrorResult& aRv) {
99 nsCOMPtr<nsIInputStream> stream;
100 aBlob.CreateInputStream(getter_AddRefs(stream), aRv);
101 if (NS_WARN_IF(aRv.Failed())) {
102 return;
105 uint32_t numRead;
106 do {
107 char readBuf[4096];
108 aRv = SyncRead(stream, readBuf, sizeof(readBuf), &numRead);
109 if (NS_WARN_IF(aRv.Failed())) {
110 return;
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);
117 return;
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())) {
128 return;
131 nsCString sniffBuf;
132 if (!sniffBuf.SetLength(3, fallible)) {
133 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
134 return;
137 uint32_t numRead = 0;
138 aRv = SyncRead(stream, sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
139 if (NS_WARN_IF(aRv.Failed())) {
140 return;
143 // No data, we don't need to continue.
144 if (numRead == 0) {
145 aResult.Truncate();
146 return;
149 // Try the API argument.
150 const Encoding* encoding =
151 aEncoding.WasPassed() ? Encoding::ForLabel(aEncoding.Value()) : nullptr;
152 if (!encoding) {
153 // API argument failed. Try the type property of the blob.
154 nsAutoString type16;
155 aBlob.GetType(type16);
156 NS_ConvertUTF16toUTF8 type(type16);
157 nsAutoCString specifiedCharset;
158 bool haveCharset;
159 int32_t charsetStart, charsetEnd;
160 NS_ExtractCharsetFromContentType(type, specifiedCharset, &haveCharset,
161 &charsetStart, &charsetEnd);
162 encoding = Encoding::ForLabel(specifiedCharset);
163 if (!encoding) {
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);
182 return;
185 nsCOMPtr<nsIInputStream> sniffStringStream;
186 aRv = NS_NewCStringInputStream(getter_AddRefs(sniffStringStream), sniffBuf);
187 if (NS_WARN_IF(aRv.Failed())) {
188 return;
191 aRv = multiplexStream->AppendStream(sniffStringStream);
192 if (NS_WARN_IF(aRv.Failed())) {
193 return;
196 uint64_t blobSize = aBlob.GetSize(aRv);
197 if (NS_WARN_IF(aRv.Failed())) {
198 return;
201 nsCOMPtr<nsIInputStream> syncStream;
202 aRv = ConvertAsyncToSyncStream(blobSize - sniffBuf.Length(), stream.forget(),
203 getter_AddRefs(syncStream));
204 if (NS_WARN_IF(aRv.Failed())) {
205 return;
208 // ConvertAsyncToSyncStream returns a null syncStream if the stream has been
209 // already closed or there is nothing to read.
210 if (syncStream) {
211 aRv = multiplexStream->AppendStream(syncStream);
212 if (NS_WARN_IF(aRv.Failed())) {
213 return;
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())) {
223 return;
227 void FileReaderSync::ReadAsDataURL(Blob& aBlob, nsAString& aResult,
228 ErrorResult& aRv) {
229 nsAutoString scratchResult;
230 scratchResult.AssignLiteral("data:");
232 nsString contentType;
233 aBlob.GetType(contentType);
235 if (contentType.IsEmpty()) {
236 scratchResult.AppendLiteral("application/octet-stream");
237 } else {
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())) {
245 return;
248 uint64_t blobSize = aBlob.GetSize(aRv);
249 if (NS_WARN_IF(aRv.Failed())) {
250 return;
253 nsCOMPtr<nsIInputStream> syncStream;
254 aRv = ConvertAsyncToSyncStream(blobSize, stream.forget(),
255 getter_AddRefs(syncStream));
256 if (NS_WARN_IF(aRv.Failed())) {
257 return;
260 MOZ_ASSERT(syncStream);
262 uint64_t size;
263 aRv = syncStream->Available(&size);
264 if (NS_WARN_IF(aRv.Failed())) {
265 return;
268 // The file is changed in the meantime?
269 if (blobSize != size) {
270 return;
273 nsAutoString encodedData;
274 aRv = Base64EncodeInputStream(syncStream, encodedData, size);
275 if (NS_WARN_IF(aRv.Failed())) {
276 return;
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);
299 uint32_t numChars;
300 nsString result;
301 while (NS_SUCCEEDED(unicharStream->ReadString(8192, result, &numChars)) &&
302 numChars > 0) {
303 uint32_t oldLength = aResult.Length();
304 aResult.Append(result);
305 if (aResult.Length() - oldLength != result.Length()) {
306 return NS_ERROR_OUT_OF_MEMORY;
310 return rv;
313 namespace {
315 // This runnable is used to terminate the sync event loop.
316 class ReadReadyRunnable final : public WorkerSyncRunnable {
317 public:
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);
330 return true;
333 private:
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 {
340 public:
341 NS_DECL_THREADSAFE_ISUPPORTS
343 ReadCallback(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aEventTarget)
344 : mWorkerPrivate(aWorkerPrivate), mEventTarget(aEventTarget) {}
346 NS_IMETHOD
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);
354 private:
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)
368 NS_INTERFACE_MAP_END
370 } // namespace
372 nsresult FileReaderSync::SyncRead(nsIInputStream* aStream, char* aBuffer,
373 uint32_t aBufferSize,
374 uint32_t* aTotalBytesRead) {
375 MOZ_ASSERT(aStream);
376 MOZ_ASSERT(aBuffer);
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.
391 nsresult rv =
392 aStream->Read(aBuffer + *aTotalBytesRead,
393 aBufferSize - *aTotalBytesRead, &currentBytesRead);
395 // Nothing else to read.
396 if (rv == NS_BASE_STREAM_CLOSED ||
397 (NS_SUCCEEDED(rv) && currentBytesRead == 0)) {
398 return NS_OK;
401 // An error.
402 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
403 return rv;
406 // All good.
407 if (NS_SUCCEEDED(rv)) {
408 *aTotalBytesRead += currentBytesRead;
409 continue;
412 // We need to proceed async.
413 if (!asyncStream) {
414 asyncStream = do_QueryInterface(aStream);
415 if (!asyncStream) {
416 return rv;
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);
432 if (!target) {
433 target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
434 MOZ_ASSERT(target);
437 rv = asyncStream->AsyncWait(callback, 0, aBufferSize - *aTotalBytesRead,
438 target);
439 if (NS_WARN_IF(NS_FAILED(rv))) {
440 return rv;
443 if (NS_WARN_IF(NS_FAILED(syncLoop.Run()))) {
444 return NS_ERROR_DOM_INVALID_STATE_ERR;
448 return NS_OK;
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);
459 if (!asyncStream) {
460 return NS_NewBufferedInputStream(aSyncStream, asyncInputStream.forget(),
461 4096);
464 nsAutoCString buffer;
465 if (!buffer.SetLength(aStreamSize, fallible)) {
466 return NS_ERROR_OUT_OF_MEMORY;
469 uint32_t read;
470 nsresult rv =
471 SyncRead(asyncInputStream, buffer.BeginWriting(), aStreamSize, &read);
472 if (NS_WARN_IF(NS_FAILED(rv))) {
473 return 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))) {
482 return rv;
485 return NS_OK;