LCOV - code coverage report
Current view: top level - dom/workers - FileReaderSync.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 230 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 19 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "FileReaderSync.h"
       8             : 
       9             : #include "jsfriendapi.h"
      10             : #include "mozilla/Unused.h"
      11             : #include "mozilla/Base64.h"
      12             : #include "mozilla/dom/File.h"
      13             : #include "mozilla/Encoding.h"
      14             : #include "mozilla/dom/FileReaderSyncBinding.h"
      15             : #include "nsCExternalHandlerService.h"
      16             : #include "nsComponentManagerUtils.h"
      17             : #include "nsCOMPtr.h"
      18             : #include "nsDOMClassInfoID.h"
      19             : #include "nsError.h"
      20             : #include "nsIConverterInputStream.h"
      21             : #include "nsIInputStream.h"
      22             : #include "nsIMultiplexInputStream.h"
      23             : #include "nsStreamUtils.h"
      24             : #include "nsStringStream.h"
      25             : #include "nsISupportsImpl.h"
      26             : #include "nsNetUtil.h"
      27             : #include "nsServiceManagerUtils.h"
      28             : #include "nsIAsyncInputStream.h"
      29             : #include "WorkerPrivate.h"
      30             : #include "WorkerRunnable.h"
      31             : 
      32             : #include "RuntimeService.h"
      33             : 
      34             : using namespace mozilla;
      35             : using namespace mozilla::dom;
      36             : using mozilla::dom::Optional;
      37             : using mozilla::dom::GlobalObject;
      38             : 
      39             : // static
      40             : already_AddRefed<FileReaderSync>
      41           0 : FileReaderSync::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
      42             : {
      43           0 :   RefPtr<FileReaderSync> frs = new FileReaderSync();
      44             : 
      45           0 :   return frs.forget();
      46             : }
      47             : 
      48             : bool
      49           0 : FileReaderSync::WrapObject(JSContext* aCx,
      50             :                            JS::Handle<JSObject*> aGivenProto,
      51             :                            JS::MutableHandle<JSObject*> aReflector)
      52             : {
      53           0 :   return FileReaderSyncBinding::Wrap(aCx, this, aGivenProto, aReflector);
      54             : }
      55             : 
      56             : void
      57           0 : FileReaderSync::ReadAsArrayBuffer(JSContext* aCx,
      58             :                                   JS::Handle<JSObject*> aScopeObj,
      59             :                                   Blob& aBlob,
      60             :                                   JS::MutableHandle<JSObject*> aRetval,
      61             :                                   ErrorResult& aRv)
      62             : {
      63           0 :   uint64_t blobSize = aBlob.GetSize(aRv);
      64           0 :   if (NS_WARN_IF(aRv.Failed())) {
      65           0 :     return;
      66             :   }
      67             : 
      68           0 :   UniquePtr<char[], JS::FreePolicy> bufferData(js_pod_malloc<char>(blobSize));
      69           0 :   if (!bufferData) {
      70           0 :     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
      71           0 :     return;
      72             :   }
      73             : 
      74           0 :   nsCOMPtr<nsIInputStream> stream;
      75           0 :   aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
      76           0 :   if (NS_WARN_IF(aRv.Failed())) {
      77           0 :     return;
      78             :   }
      79             : 
      80             :   uint32_t numRead;
      81           0 :   aRv = SyncRead(stream, bufferData.get(), blobSize, &numRead);
      82           0 :   if (NS_WARN_IF(aRv.Failed())) {
      83           0 :     return;
      84             :   }
      85             : 
      86             :   // The file is changed in the meantime?
      87           0 :   if (numRead != blobSize) {
      88           0 :     aRv.Throw(NS_ERROR_FAILURE);
      89           0 :     return;
      90             :   }
      91             : 
      92           0 :   JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aCx, blobSize, bufferData.get());
      93           0 :   if (!arrayBuffer) {
      94           0 :     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
      95           0 :     return;
      96             :   }
      97             :   // arrayBuffer takes the ownership when it is not null. Otherwise we
      98             :   // need to release it explicitly.
      99           0 :   mozilla::Unused << bufferData.release();
     100             : 
     101           0 :   aRetval.set(arrayBuffer);
     102             : }
     103             : 
     104             : void
     105           0 : FileReaderSync::ReadAsBinaryString(Blob& aBlob,
     106             :                                    nsAString& aResult,
     107             :                                    ErrorResult& aRv)
     108             : {
     109           0 :   nsCOMPtr<nsIInputStream> stream;
     110           0 :   aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
     111           0 :   if (NS_WARN_IF(aRv.Failed())) {
     112           0 :     return;
     113             :   }
     114             : 
     115             :   uint32_t numRead;
     116           0 :   do {
     117             :     char readBuf[4096];
     118           0 :     aRv = SyncRead(stream, readBuf, sizeof(readBuf), &numRead);
     119           0 :     if (NS_WARN_IF(aRv.Failed())) {
     120           0 :       return;
     121             :     }
     122             : 
     123           0 :     uint32_t oldLength = aResult.Length();
     124           0 :     AppendASCIItoUTF16(Substring(readBuf, readBuf + numRead), aResult);
     125           0 :     if (aResult.Length() - oldLength != numRead) {
     126           0 :       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     127           0 :       return;
     128             :     }
     129           0 :   } while (numRead > 0);
     130             : }
     131             : 
     132             : void
     133           0 : FileReaderSync::ReadAsText(Blob& aBlob,
     134             :                            const Optional<nsAString>& aEncoding,
     135             :                            nsAString& aResult,
     136             :                            ErrorResult& aRv)
     137             : {
     138           0 :   nsCOMPtr<nsIInputStream> stream;
     139           0 :   aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
     140           0 :   if (NS_WARN_IF(aRv.Failed())) {
     141           0 :     return;
     142             :   }
     143             : 
     144           0 :   nsCString sniffBuf;
     145           0 :   if (!sniffBuf.SetLength(3, fallible)) {
     146           0 :     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     147           0 :     return;
     148             :   }
     149             : 
     150           0 :   uint32_t numRead = 0;
     151           0 :   aRv = SyncRead(stream, sniffBuf.BeginWriting(), sniffBuf.Length(), &numRead);
     152           0 :   if (NS_WARN_IF(aRv.Failed())) {
     153           0 :     return;
     154             :   }
     155             : 
     156             :   // No data, we don't need to continue.
     157           0 :   if (numRead == 0) {
     158           0 :     aResult.Truncate();
     159           0 :     return;
     160             :   }
     161             : 
     162             :   // Try the API argument.
     163           0 :   const Encoding* encoding = aEncoding.WasPassed() ?
     164           0 :     Encoding::ForLabel(aEncoding.Value()) : nullptr;
     165           0 :   if (!encoding) {
     166             :     // API argument failed. Try the type property of the blob.
     167           0 :     nsAutoString type16;
     168           0 :     aBlob.GetType(type16);
     169           0 :     NS_ConvertUTF16toUTF8 type(type16);
     170           0 :     nsAutoCString specifiedCharset;
     171             :     bool haveCharset;
     172             :     int32_t charsetStart, charsetEnd;
     173             :     NS_ExtractCharsetFromContentType(type,
     174             :                                      specifiedCharset,
     175             :                                      &haveCharset,
     176             :                                      &charsetStart,
     177           0 :                                      &charsetEnd);
     178           0 :     encoding = Encoding::ForLabel(specifiedCharset);
     179           0 :     if (!encoding) {
     180             :       // Type property failed. Use UTF-8.
     181           0 :       encoding = UTF_8_ENCODING;
     182             :     }
     183             :   }
     184             : 
     185           0 :   if (numRead < sniffBuf.Length()) {
     186           0 :     sniffBuf.Truncate(numRead);
     187             :   }
     188             : 
     189             :   // Let's recreate the full stream using a:
     190             :   // multiplexStream(syncStream + original stream)
     191             :   // In theory, we could try to see if the inputStream is a nsISeekableStream,
     192             :   // but this doesn't work correctly for nsPipe3 - See bug 1349570.
     193             : 
     194             :   nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
     195           0 :     do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
     196           0 :   if (NS_WARN_IF(!multiplexStream)) {
     197           0 :     aRv.Throw(NS_ERROR_FAILURE);
     198           0 :     return;
     199             :   }
     200             : 
     201           0 :   nsCOMPtr<nsIInputStream> sniffStringStream;
     202           0 :   aRv = NS_NewCStringInputStream(getter_AddRefs(sniffStringStream), sniffBuf);
     203           0 :   if (NS_WARN_IF(aRv.Failed())) {
     204           0 :     return;
     205             :   }
     206             : 
     207           0 :   aRv = multiplexStream->AppendStream(sniffStringStream);
     208           0 :   if (NS_WARN_IF(aRv.Failed())) {
     209           0 :     return;
     210             :   }
     211             : 
     212           0 :   nsCOMPtr<nsIInputStream> syncStream;
     213           0 :   aRv = ConvertAsyncToSyncStream(stream, getter_AddRefs(syncStream));
     214           0 :   if (NS_WARN_IF(aRv.Failed())) {
     215           0 :     return;
     216             :   }
     217             : 
     218             :   // ConvertAsyncToSyncStream returns a null syncStream if the stream has been
     219             :   // already closed or there is nothing to read.
     220           0 :   if (syncStream) {
     221           0 :     aRv = multiplexStream->AppendStream(syncStream);
     222           0 :     if (NS_WARN_IF(aRv.Failed())) {
     223           0 :       return;
     224             :     }
     225             :   }
     226             : 
     227           0 :   nsAutoCString charset;
     228           0 :   encoding->Name(charset);
     229           0 :   aRv = ConvertStream(multiplexStream, charset.get(), aResult);
     230           0 :   if (NS_WARN_IF(aRv.Failed())) {
     231           0 :     return;
     232             :   }
     233             : }
     234             : 
     235             : void
     236           0 : FileReaderSync::ReadAsDataURL(Blob& aBlob, nsAString& aResult,
     237             :                               ErrorResult& aRv)
     238             : {
     239           0 :   nsAutoString scratchResult;
     240           0 :   scratchResult.AssignLiteral("data:");
     241             : 
     242           0 :   nsString contentType;
     243           0 :   aBlob.GetType(contentType);
     244             : 
     245           0 :   if (contentType.IsEmpty()) {
     246           0 :     scratchResult.AppendLiteral("application/octet-stream");
     247             :   } else {
     248           0 :     scratchResult.Append(contentType);
     249             :   }
     250           0 :   scratchResult.AppendLiteral(";base64,");
     251             : 
     252           0 :   nsCOMPtr<nsIInputStream> stream;
     253           0 :   aBlob.GetInternalStream(getter_AddRefs(stream), aRv);
     254           0 :   if (NS_WARN_IF(aRv.Failed())){
     255           0 :     return;
     256             :   }
     257             : 
     258           0 :   nsCOMPtr<nsIInputStream> syncStream;
     259           0 :   aRv = ConvertAsyncToSyncStream(stream, getter_AddRefs(syncStream));
     260           0 :   if (NS_WARN_IF(aRv.Failed())) {
     261           0 :     return;
     262             :   }
     263             : 
     264           0 :   MOZ_ASSERT(syncStream);
     265             : 
     266             :   uint64_t size;
     267           0 :   aRv = syncStream->Available(&size);
     268           0 :   if (NS_WARN_IF(aRv.Failed())) {
     269           0 :     return;
     270             :   }
     271             : 
     272           0 :   uint64_t blobSize = aBlob.GetSize(aRv);
     273           0 :   if (NS_WARN_IF(aRv.Failed())){
     274           0 :     return;
     275             :   }
     276             : 
     277             :   // The file is changed in the meantime?
     278           0 :   if (blobSize != size) {
     279           0 :     return;
     280             :   }
     281             : 
     282           0 :   nsAutoString encodedData;
     283           0 :   aRv = Base64EncodeInputStream(syncStream, encodedData, size);
     284           0 :   if (NS_WARN_IF(aRv.Failed())){
     285           0 :     return;
     286             :   }
     287             : 
     288           0 :   scratchResult.Append(encodedData);
     289             : 
     290           0 :   aResult = scratchResult;
     291             : }
     292             : 
     293             : nsresult
     294           0 : FileReaderSync::ConvertStream(nsIInputStream *aStream,
     295             :                               const char *aCharset,
     296             :                               nsAString &aResult)
     297             : {
     298             :   nsCOMPtr<nsIConverterInputStream> converterStream =
     299           0 :     do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
     300           0 :   NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE);
     301             : 
     302           0 :   nsresult rv = converterStream->Init(aStream, aCharset, 8192,
     303           0 :                   nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
     304           0 :   NS_ENSURE_SUCCESS(rv, rv);
     305             : 
     306             :   nsCOMPtr<nsIUnicharInputStream> unicharStream =
     307           0 :     do_QueryInterface(converterStream);
     308           0 :   NS_ENSURE_TRUE(unicharStream, NS_ERROR_FAILURE);
     309             : 
     310             :   uint32_t numChars;
     311           0 :   nsString result;
     312           0 :   while (NS_SUCCEEDED(unicharStream->ReadString(8192, result, &numChars)) &&
     313           0 :          numChars > 0) {
     314           0 :     uint32_t oldLength = aResult.Length();
     315           0 :     aResult.Append(result);
     316           0 :     if (aResult.Length() - oldLength != result.Length()) {
     317           0 :       return NS_ERROR_OUT_OF_MEMORY;
     318             :     }
     319             :   }
     320             : 
     321           0 :   return rv;
     322             : }
     323             : 
     324             : namespace {
     325             : 
     326             : // This runnable is used to terminate the sync event loop.
     327             : class ReadReadyRunnable final : public WorkerSyncRunnable
     328             : {
     329             : public:
     330           0 :   ReadReadyRunnable(WorkerPrivate* aWorkerPrivate,
     331             :                     nsIEventTarget* aSyncLoopTarget)
     332           0 :     : WorkerSyncRunnable(aWorkerPrivate, aSyncLoopTarget)
     333           0 :   {}
     334             : 
     335             :   bool
     336           0 :   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
     337             :   {
     338           0 :     aWorkerPrivate->AssertIsOnWorkerThread();
     339           0 :     MOZ_ASSERT(mSyncLoopTarget);
     340             : 
     341           0 :     nsCOMPtr<nsIEventTarget> syncLoopTarget;
     342           0 :     mSyncLoopTarget.swap(syncLoopTarget);
     343             : 
     344           0 :     aWorkerPrivate->StopSyncLoop(syncLoopTarget, true);
     345           0 :     return true;
     346             :   }
     347             : 
     348             : private:
     349           0 :   ~ReadReadyRunnable()
     350           0 :   {}
     351             : };
     352             : 
     353             : // This class implements nsIInputStreamCallback and it will be called when the
     354             : // stream is ready to be read.
     355             : class ReadCallback final : public nsIInputStreamCallback
     356             : {
     357             : public:
     358             :   NS_DECL_THREADSAFE_ISUPPORTS
     359             : 
     360           0 :   ReadCallback(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aEventTarget)
     361           0 :     : mWorkerPrivate(aWorkerPrivate)
     362           0 :     , mEventTarget(aEventTarget)
     363           0 :   {}
     364             : 
     365             :   NS_IMETHOD
     366           0 :   OnInputStreamReady(nsIAsyncInputStream* aStream) override
     367             :   {
     368             :     // I/O Thread. Now we need to block the sync event loop.
     369             :     RefPtr<ReadReadyRunnable> runnable =
     370           0 :       new ReadReadyRunnable(mWorkerPrivate, mEventTarget);
     371           0 :     return mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
     372             :   }
     373             : 
     374             : private:
     375           0 :   ~ReadCallback()
     376           0 :   {}
     377             : 
     378             :   // The worker is kept alive because of the sync event loop.
     379             :   WorkerPrivate* mWorkerPrivate;
     380             :   nsCOMPtr<nsIEventTarget> mEventTarget;
     381             : };
     382             : 
     383           0 : NS_IMPL_ADDREF(ReadCallback);
     384           0 : NS_IMPL_RELEASE(ReadCallback);
     385             : 
     386           0 : NS_INTERFACE_MAP_BEGIN(ReadCallback)
     387           0 :   NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
     388           0 :   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStreamCallback)
     389           0 : NS_INTERFACE_MAP_END
     390             : 
     391             : } // anonymous
     392             : 
     393             : nsresult
     394           0 : FileReaderSync::SyncRead(nsIInputStream* aStream, char* aBuffer,
     395             :                          uint32_t aBufferSize, uint32_t* aRead)
     396             : {
     397           0 :   MOZ_ASSERT(aStream);
     398           0 :   MOZ_ASSERT(aBuffer);
     399           0 :   MOZ_ASSERT(aRead);
     400             : 
     401             :   // Let's try to read, directly.
     402           0 :   nsresult rv = aStream->Read(aBuffer, aBufferSize, aRead);
     403             : 
     404             :   // Nothing else to read.
     405           0 :   if (rv == NS_BASE_STREAM_CLOSED ||
     406           0 :       (NS_SUCCEEDED(rv) && *aRead == 0)) {
     407           0 :     return NS_OK;
     408             :   }
     409             : 
     410             :   // An error.
     411           0 :   if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
     412           0 :     return rv;
     413             :   }
     414             : 
     415             :   // All good.
     416           0 :   if (NS_SUCCEEDED(rv)) {
     417             :     // Not enough data, let's read recursively.
     418           0 :     if (*aRead != aBufferSize) {
     419           0 :       uint32_t byteRead = 0;
     420           0 :       rv = SyncRead(aStream, aBuffer + *aRead, aBufferSize - *aRead, &byteRead);
     421           0 :       if (NS_WARN_IF(NS_FAILED(rv))) {
     422           0 :         return rv;
     423             :       }
     424             : 
     425           0 :       *aRead += byteRead;
     426             :     }
     427             : 
     428           0 :     return NS_OK;
     429             :   }
     430             : 
     431             :   // We need to proceed async.
     432           0 :   nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
     433           0 :   if (!asyncStream) {
     434           0 :     return rv;
     435             :   }
     436             : 
     437           0 :   WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
     438           0 :   MOZ_ASSERT(workerPrivate);
     439             : 
     440           0 :   AutoSyncLoopHolder syncLoop(workerPrivate, Closing);
     441             : 
     442           0 :   nsCOMPtr<nsIEventTarget> syncLoopTarget = syncLoop.GetEventTarget();
     443           0 :   if (!syncLoopTarget) {
     444             :     // SyncLoop creation can fail if the worker is shutting down.
     445           0 :     return NS_ERROR_DOM_INVALID_STATE_ERR;
     446             :   }
     447             : 
     448             :   RefPtr<ReadCallback> callback =
     449           0 :     new ReadCallback(workerPrivate, syncLoopTarget);
     450             : 
     451             :   nsCOMPtr<nsIEventTarget> target =
     452           0 :     do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
     453           0 :   MOZ_ASSERT(target);
     454             : 
     455           0 :   rv = asyncStream->AsyncWait(callback, 0, aBufferSize, target);
     456           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     457           0 :     return rv;
     458             :   }
     459             : 
     460           0 :   if (!syncLoop.Run()) {
     461           0 :     return NS_ERROR_DOM_INVALID_STATE_ERR;
     462             :   }
     463             : 
     464             :   // Now, we can try to read again.
     465           0 :   return SyncRead(aStream, aBuffer, aBufferSize, aRead);
     466             : }
     467             : 
     468             : nsresult
     469           0 : FileReaderSync::ConvertAsyncToSyncStream(nsIInputStream* aAsyncStream,
     470             :                                          nsIInputStream** aSyncStream)
     471             : {
     472             :   // If the stream is not async, we just need it to be bufferable.
     473           0 :   nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aAsyncStream);
     474           0 :   if (!asyncStream) {
     475           0 :     return NS_NewBufferedInputStream(aSyncStream, aAsyncStream, 4096);
     476             :   }
     477             : 
     478             :   uint64_t length;
     479           0 :   nsresult rv = aAsyncStream->Available(&length);
     480           0 :   if (rv == NS_BASE_STREAM_CLOSED) {
     481             :     // The stream has already been closed. Nothing to do.
     482           0 :     *aSyncStream = nullptr;
     483           0 :     return NS_OK;
     484             :   }
     485             : 
     486           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     487           0 :     return rv;
     488             :   }
     489             : 
     490           0 :   nsAutoCString buffer;
     491           0 :   if (!buffer.SetLength(length, fallible)) {
     492           0 :     return NS_ERROR_OUT_OF_MEMORY;
     493             :   }
     494             : 
     495             :   uint32_t read;
     496           0 :   rv = SyncRead(aAsyncStream, buffer.BeginWriting(), length, &read);
     497           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     498           0 :     return rv;
     499             :   }
     500             : 
     501           0 :   if (read != length) {
     502           0 :     return NS_ERROR_FAILURE;
     503             :   }
     504             : 
     505           0 :   rv = NS_NewCStringInputStream(aSyncStream, buffer);
     506           0 :   if (NS_WARN_IF(NS_FAILED(rv))) {
     507           0 :     return rv;
     508             :   }
     509             : 
     510           0 :   return NS_OK;
     511             : }

Generated by: LCOV version 1.13