LCOV - code coverage report
Current view: top level - dom/script - ScriptLoadHandler.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 114 186 61.3 %
Date: 2017-07-14 16:53:18 Functions: 12 13 92.3 %
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 "ScriptLoadHandler.h"
       8             : #include "ScriptLoader.h"
       9             : #include "ScriptTrace.h"
      10             : 
      11             : #include "nsContentUtils.h"
      12             : 
      13             : #include "mozilla/Telemetry.h"
      14             : 
      15             : namespace mozilla {
      16             : namespace dom {
      17             : 
      18             : #undef LOG
      19             : #define LOG(args) \
      20             :   MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
      21             : 
      22             : #define LOG_ENABLED() \
      23             :   MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
      24             : 
      25           4 : ScriptLoadHandler::ScriptLoadHandler(ScriptLoader* aScriptLoader,
      26             :                                      ScriptLoadRequest* aRequest,
      27           4 :                                      SRICheckDataVerifier* aSRIDataVerifier)
      28             :   : mScriptLoader(aScriptLoader),
      29             :     mRequest(aRequest),
      30             :     mSRIDataVerifier(aSRIDataVerifier),
      31             :     mSRIStatus(NS_OK),
      32           4 :     mDecoder()
      33             : {
      34           4 :   MOZ_ASSERT(mRequest->IsUnknownDataType());
      35           4 :   MOZ_ASSERT(mRequest->IsLoading());
      36           4 : }
      37             : 
      38           8 : ScriptLoadHandler::~ScriptLoadHandler()
      39          12 : {}
      40             : 
      41          28 : NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
      42             : 
      43             : NS_IMETHODIMP
      44           4 : ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
      45             :                                      nsISupports* aContext,
      46             :                                      uint32_t aDataLength,
      47             :                                      const uint8_t* aData,
      48             :                                      uint32_t* aConsumedLength)
      49             : {
      50           4 :   if (mRequest->IsCanceled()) {
      51             :     // If request cancelled, ignore any incoming data.
      52           0 :     *aConsumedLength = aDataLength;
      53           0 :     return NS_OK;
      54             :   }
      55             : 
      56           4 :   nsresult rv = NS_OK;
      57           4 :   if (mRequest->IsUnknownDataType()) {
      58           4 :     rv = EnsureKnownDataType(aLoader);
      59           4 :     NS_ENSURE_SUCCESS(rv, rv);
      60             :   }
      61             : 
      62           4 :   if (mRequest->IsSource()) {
      63           4 :     if (!EnsureDecoder(aLoader, aData, aDataLength,
      64             :                        /* aEndOfStream = */ false)) {
      65           0 :       return NS_OK;
      66             :     }
      67             : 
      68             :     // Below we will/shall consume entire data chunk.
      69           4 :     *aConsumedLength = aDataLength;
      70             : 
      71             :     // Decoder has already been initialized. -- trying to decode all loaded bytes.
      72           4 :     rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ false);
      73           4 :     NS_ENSURE_SUCCESS(rv, rv);
      74             : 
      75             :     // If SRI is required for this load, appending new bytes to the hash.
      76           4 :     if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
      77           0 :       mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
      78             :     }
      79             :   } else {
      80           0 :     MOZ_ASSERT(mRequest->IsBytecode());
      81           0 :     if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
      82           0 :       return NS_ERROR_OUT_OF_MEMORY;
      83             :     }
      84             : 
      85           0 :     *aConsumedLength = aDataLength;
      86           0 :     rv = MaybeDecodeSRI();
      87           0 :     if (NS_FAILED(rv)) {
      88           0 :       nsCOMPtr<nsIRequest> channelRequest;
      89           0 :       aLoader->GetRequest(getter_AddRefs(channelRequest));
      90           0 :       return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
      91             :     }
      92             :   }
      93             : 
      94           4 :   return rv;
      95             : }
      96             : 
      97             : nsresult
      98           8 : ScriptLoadHandler::DecodeRawData(const uint8_t* aData,
      99             :                                  uint32_t aDataLength,
     100             :                                  bool aEndOfStream)
     101             : {
     102           8 :   CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aDataLength);
     103           8 :   if (!needed.isValid()) {
     104           0 :     return NS_ERROR_OUT_OF_MEMORY;
     105             :   }
     106             : 
     107           8 :   uint32_t haveRead = mRequest->mScriptText.length();
     108             : 
     109           8 :   CheckedInt<uint32_t> capacity = haveRead;
     110           8 :   capacity += needed.value();
     111             : 
     112           8 :   if (!capacity.isValid() || !mRequest->mScriptText.reserve(capacity.value())) {
     113           0 :     return NS_ERROR_OUT_OF_MEMORY;
     114             :   }
     115             : 
     116             :   uint32_t result;
     117             :   size_t read;
     118             :   size_t written;
     119             :   bool hadErrors;
     120          24 :   Tie(result, read, written, hadErrors) = mDecoder->DecodeToUTF16(
     121             :     MakeSpan(aData, aDataLength),
     122           8 :     MakeSpan(mRequest->mScriptText.begin() + haveRead, needed.value()),
     123           8 :     aEndOfStream);
     124           8 :   MOZ_ASSERT(result == kInputEmpty);
     125           8 :   MOZ_ASSERT(read == aDataLength);
     126           8 :   MOZ_ASSERT(written <= needed.value());
     127             :   Unused << hadErrors;
     128             : 
     129           8 :   haveRead += written;
     130           8 :   MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
     131           8 :   MOZ_ALWAYS_TRUE(mRequest->mScriptText.resizeUninitialized(haveRead));
     132             : 
     133           8 :   return NS_OK;
     134             : }
     135             : 
     136             : bool
     137           8 : ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
     138             :                                  const uint8_t* aData,
     139             :                                  uint32_t aDataLength,
     140             :                                  bool aEndOfStream)
     141             : {
     142             :   // Check if decoder has already been created.
     143           8 :   if (mDecoder) {
     144           4 :     return true;
     145             :   }
     146             : 
     147           8 :   nsAutoCString charset;
     148           4 :   if (!EnsureDecoder(aLoader, aData, aDataLength, aEndOfStream, charset)) {
     149           0 :     return false;
     150             :   }
     151           4 :   if (charset.Length() == 0) {
     152           0 :     charset = "?";
     153             :   }
     154             :   mozilla::Telemetry::Accumulate(mozilla::Telemetry::DOM_SCRIPT_SRC_ENCODING,
     155           4 :     charset);
     156           4 :   return true;
     157             : }
     158             : 
     159             : bool
     160           4 : ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
     161             :                                  const uint8_t* aData,
     162             :                                  uint32_t aDataLength,
     163             :                                  bool aEndOfStream,
     164             :                                  nsCString& oCharset)
     165             : {
     166             :   // JavaScript modules are always UTF-8.
     167           4 :   if (mRequest->IsModuleRequest()) {
     168           0 :     oCharset = "UTF-8";
     169           0 :     mDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
     170           0 :     return true;
     171             :   }
     172             : 
     173             :   // Determine if BOM check should be done.  This occurs either
     174             :   // if end-of-stream has been reached, or at least 3 bytes have
     175             :   // been read from input.
     176           4 :   if (!aEndOfStream && (aDataLength < 3)) {
     177           0 :     return false;
     178             :   }
     179             : 
     180             :   // Do BOM detection.
     181             :   const Encoding* encoding;
     182             :   size_t bomLength;
     183           4 :   Tie(encoding, bomLength) = Encoding::ForBOM(MakeSpan(aData, aDataLength));
     184           4 :   if (encoding) {
     185           0 :     mDecoder = encoding->NewDecoderWithBOMRemoval();
     186           0 :     encoding->Name(oCharset);
     187           0 :     return true;
     188             :   }
     189             : 
     190             :   // BOM detection failed, check content stream for charset.
     191           8 :   nsCOMPtr<nsIRequest> req;
     192           4 :   nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
     193           4 :   NS_ASSERTION(req, "StreamLoader's request went away prematurely");
     194           4 :   NS_ENSURE_SUCCESS(rv, false);
     195             : 
     196           8 :   nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
     197             : 
     198           4 :   if (channel) {
     199           6 :     nsAutoCString label;
     200           8 :     if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
     201           4 :         (encoding = Encoding::ForLabel(label))) {
     202           2 :       mDecoder = encoding->NewDecoderWithoutBOMHandling();
     203           2 :       encoding->Name(oCharset);
     204           2 :       return true;
     205             :     }
     206             :   }
     207             : 
     208             :   // Check the hint charset from the script element or preload
     209             :   // request.
     210           4 :   nsAutoString hintCharset;
     211           2 :   if (!mRequest->IsPreload()) {
     212           2 :     mRequest->mElement->GetScriptCharset(hintCharset);
     213             :   } else {
     214             :     nsTArray<ScriptLoader::PreloadInfo>::index_type i =
     215           0 :       mScriptLoader->mPreloads.IndexOf(mRequest, 0,
     216           0 :             ScriptLoader::PreloadRequestComparator());
     217             : 
     218           0 :     NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
     219             :                  "Incorrect preload bookkeeping");
     220           0 :     hintCharset = mScriptLoader->mPreloads[i].mCharset;
     221             :   }
     222             : 
     223           2 :   if ((encoding = Encoding::ForLabel(hintCharset))) {
     224           0 :     mDecoder = encoding->NewDecoderWithoutBOMHandling();
     225           0 :     encoding->Name(oCharset);
     226           0 :     return true;
     227             :   }
     228             : 
     229             :   // Get the charset from the charset of the document.
     230           2 :   if (mScriptLoader->mDocument) {
     231           2 :     encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
     232           2 :     mDecoder = encoding->NewDecoderWithoutBOMHandling();
     233           2 :     encoding->Name(oCharset);
     234           2 :     return true;
     235             :   }
     236             : 
     237             :   // Curiously, there are various callers that don't pass aDocument. The
     238             :   // fallback in the old code was ISO-8859-1, which behaved like
     239             :   // windows-1252.
     240           0 :   oCharset = "windows-1252";
     241           0 :   mDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
     242           0 :   return true;
     243             : }
     244             : 
     245             : nsresult
     246           0 : ScriptLoadHandler::MaybeDecodeSRI()
     247             : {
     248           0 :   if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() || NS_FAILED(mSRIStatus)) {
     249           0 :     return NS_OK;
     250             :   }
     251             : 
     252             :   // Skip until the content is large enough to be decoded.
     253           0 :   if (mRequest->mScriptBytecode.length() <= mSRIDataVerifier->DataSummaryLength()) {
     254           0 :     return NS_OK;
     255             :   }
     256             : 
     257           0 :   mSRIStatus = mSRIDataVerifier->ImportDataSummary(
     258           0 :     mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin());
     259             : 
     260           0 :   if (NS_FAILED(mSRIStatus)) {
     261             :     // We are unable to decode the hash contained in the alternate data which
     262             :     // contains the bytecode, or it does not use the same algorithm.
     263           0 :     LOG(("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart request"));
     264           0 :     return mSRIStatus;
     265             :   }
     266             : 
     267           0 :   mRequest->mBytecodeOffset = mSRIDataVerifier->DataSummaryLength();
     268           0 :   return NS_OK;
     269             : }
     270             : 
     271             : nsresult
     272           4 : ScriptLoadHandler::EnsureKnownDataType(nsIIncrementalStreamLoader* aLoader)
     273             : {
     274           4 :   MOZ_ASSERT(mRequest->IsUnknownDataType());
     275           4 :   MOZ_ASSERT(mRequest->IsLoading());
     276           4 :   if (mRequest->IsLoadingSource()) {
     277           0 :     mRequest->mDataType = ScriptLoadRequest::DataType::Source;
     278           0 :     TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
     279           0 :     return NS_OK;
     280             :   }
     281             : 
     282           8 :   nsCOMPtr<nsIRequest> req;
     283           4 :   nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
     284           4 :   MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
     285           4 :   NS_ENSURE_SUCCESS(rv, rv);
     286             : 
     287           8 :   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
     288           4 :   if (cic) {
     289           4 :     nsAutoCString altDataType;
     290           2 :     cic->GetAlternativeDataType(altDataType);
     291           2 :     if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
     292           0 :       mRequest->mDataType = ScriptLoadRequest::DataType::Bytecode;
     293           0 :       TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_bytecode");
     294             :     } else {
     295           2 :       MOZ_ASSERT(altDataType.IsEmpty());
     296           2 :       mRequest->mDataType = ScriptLoadRequest::DataType::Source;
     297           2 :       TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
     298             :     }
     299             :   } else {
     300           2 :     mRequest->mDataType = ScriptLoadRequest::DataType::Source;
     301           2 :     TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
     302             :   }
     303           4 :   MOZ_ASSERT(!mRequest->IsUnknownDataType());
     304           4 :   MOZ_ASSERT(mRequest->IsLoading());
     305           4 :   return NS_OK;
     306             : }
     307             : 
     308             : NS_IMETHODIMP
     309           4 : ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
     310             :                                     nsISupports* aContext,
     311             :                                     nsresult aStatus,
     312             :                                     uint32_t aDataLength,
     313             :                                     const uint8_t* aData)
     314             : {
     315           4 :   nsresult rv = NS_OK;
     316           4 :   if (LOG_ENABLED()) {
     317           0 :     nsAutoCString url;
     318           0 :     mRequest->mURI->GetAsciiSpec(url);
     319           0 :     LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)",
     320             :          mRequest.get(), url.get()));
     321             :   }
     322             : 
     323           8 :   nsCOMPtr<nsIRequest> channelRequest;
     324           4 :   aLoader->GetRequest(getter_AddRefs(channelRequest));
     325             : 
     326           4 :   if (!mRequest->IsCanceled()) {
     327           4 :     if (mRequest->IsUnknownDataType()) {
     328           0 :       rv = EnsureKnownDataType(aLoader);
     329           0 :       NS_ENSURE_SUCCESS(rv, rv);
     330             :     }
     331             : 
     332           4 :     if (mRequest->IsSource()) {
     333             :       DebugOnly<bool> encoderSet =
     334           8 :         EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
     335           4 :       MOZ_ASSERT(encoderSet);
     336           4 :       rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ true);
     337           4 :       NS_ENSURE_SUCCESS(rv, rv);
     338             : 
     339           4 :       LOG(("ScriptLoadRequest (%p): Source length = %u",
     340             :            mRequest.get(), unsigned(mRequest->mScriptText.length())));
     341             : 
     342             :       // If SRI is required for this load, appending new bytes to the hash.
     343           4 :       if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
     344           0 :         mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
     345             :       }
     346             :     } else {
     347           0 :       MOZ_ASSERT(mRequest->IsBytecode());
     348           0 :       if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
     349           0 :         return NS_ERROR_OUT_OF_MEMORY;
     350             :       }
     351             : 
     352           0 :       LOG(("ScriptLoadRequest (%p): Bytecode length = %u",
     353             :            mRequest.get(), unsigned(mRequest->mScriptBytecode.length())));
     354             : 
     355             :       // If we abort while decoding the SRI, we fallback on explictly requesting
     356             :       // the source. Thus, we should not continue in
     357             :       // ScriptLoader::OnStreamComplete, which removes the request from the
     358             :       // waiting lists.
     359           0 :       rv = MaybeDecodeSRI();
     360           0 :       if (NS_FAILED(rv)) {
     361           0 :         return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
     362             :       }
     363             : 
     364             :       // The bytecode cache always starts with the SRI hash, thus even if there
     365             :       // is no SRI data verifier instance, we still want to skip the hash.
     366           0 :       rv = SRICheckDataVerifier::DataSummaryLength(mRequest->mScriptBytecode.length(),
     367           0 :                                                    mRequest->mScriptBytecode.begin(),
     368           0 :                                                    &mRequest->mBytecodeOffset);
     369           0 :       if (NS_FAILED(rv)) {
     370           0 :         return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
     371             :       }
     372             :     }
     373             :   }
     374             : 
     375             :   // Everything went well, keep the CacheInfoChannel alive such that we can
     376             :   // later save the bytecode on the cache entry.
     377           8 :   if (NS_SUCCEEDED(rv) && mRequest->IsSource() &&
     378           4 :       nsContentUtils::IsBytecodeCacheEnabled()) {
     379           0 :     mRequest->mCacheInfo = do_QueryInterface(channelRequest);
     380           0 :     LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p",
     381             :          mRequest.get(), mRequest->mCacheInfo.get()));
     382             :   }
     383             : 
     384             :   // we have to mediate and use mRequest.
     385           4 :   rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
     386           4 :                                        mSRIDataVerifier);
     387             : 
     388             :   // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
     389           4 :   if (NS_FAILED(rv)) {
     390           0 :     mRequest->mCacheInfo = nullptr;
     391             :   }
     392             : 
     393           4 :   return rv;
     394             : }
     395             : 
     396             : #undef LOG_ENABLED
     397             : #undef LOG
     398             : 
     399             : } // dom namespace
     400             : } // mozilla namespace

Generated by: LCOV version 1.13