ScriptLoadHandler.cpp (16668B)
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 9 #include <stdlib.h> 10 11 #include <utility> 12 13 #include "ScriptCompression.h" 14 #include "ScriptLoader.h" 15 #include "ScriptTrace.h" 16 #include "js/Transcoding.h" 17 #include "js/loader/ScriptLoadRequest.h" 18 #include "mozilla/Assertions.h" 19 #include "mozilla/CheckedInt.h" 20 #include "mozilla/DebugOnly.h" 21 #include "mozilla/Encoding.h" 22 #include "mozilla/Logging.h" 23 #include "mozilla/PerfStats.h" 24 #include "mozilla/ScopeExit.h" 25 #include "mozilla/SharedSubResourceCache.h" 26 #include "mozilla/StaticPrefs_dom.h" 27 #include "mozilla/Utf8.h" 28 #include "mozilla/Vector.h" 29 #include "mozilla/dom/CacheExpirationTime.h" 30 #include "mozilla/dom/Document.h" 31 #include "mozilla/dom/SRICheck.h" 32 #include "mozilla/dom/ScriptDecoding.h" 33 #include "nsCOMPtr.h" 34 #include "nsContentUtils.h" 35 #include "nsDebug.h" 36 #include "nsIAsyncVerifyRedirectCallback.h" 37 #include "nsICacheInfoChannel.h" 38 #include "nsIChannel.h" 39 #include "nsIHttpChannel.h" 40 #include "nsIRequest.h" 41 #include "nsIScriptElement.h" 42 #include "nsIURI.h" 43 #include "nsJSUtils.h" 44 #include "nsMimeTypes.h" 45 #include "nsString.h" 46 #include "nsTArray.h" 47 #include "zlib.h" 48 49 namespace mozilla::dom { 50 51 #undef LOG 52 #define LOG(args) \ 53 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args) 54 55 #define LOG_ENABLED() \ 56 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug) 57 58 ScriptDecoder::ScriptDecoder(const Encoding* aEncoding, 59 ScriptDecoder::BOMHandling handleBOM) { 60 if (handleBOM == BOMHandling::Ignore) { 61 mDecoder = aEncoding->NewDecoderWithoutBOMHandling(); 62 } else { 63 mDecoder = aEncoding->NewDecoderWithBOMRemoval(); 64 } 65 MOZ_ASSERT(mDecoder); 66 } 67 68 template <typename Unit> 69 nsresult ScriptDecoder::DecodeRawDataHelper( 70 JS::loader::ScriptLoadRequest* aRequest, const uint8_t* aData, 71 uint32_t aDataLength, bool aEndOfStream) { 72 CheckedInt<size_t> needed = 73 ScriptDecoding<Unit>::MaxBufferLength(mDecoder, aDataLength); 74 if (!needed.isValid()) { 75 return NS_ERROR_OUT_OF_MEMORY; 76 } 77 78 // Reference to the script source buffer which we will update. 79 JS::loader::ScriptLoadRequest::ScriptTextBuffer<Unit>& scriptText = 80 aRequest->ScriptText<Unit>(); 81 82 uint32_t haveRead = scriptText.length(); 83 84 CheckedInt<uint32_t> capacity = haveRead; 85 capacity += needed.value(); 86 87 if (!capacity.isValid() || !scriptText.resize(capacity.value())) { 88 return NS_ERROR_OUT_OF_MEMORY; 89 } 90 91 size_t written = ScriptDecoding<Unit>::DecodeInto( 92 mDecoder, Span(aData, aDataLength), 93 Span(scriptText.begin() + haveRead, needed.value()), aEndOfStream); 94 MOZ_ASSERT(written <= needed.value()); 95 96 haveRead += written; 97 MOZ_ASSERT(haveRead <= capacity.value(), 98 "mDecoder produced more data than expected"); 99 MOZ_ALWAYS_TRUE(scriptText.resize(haveRead)); 100 aRequest->SetReceivedScriptTextLength(scriptText.length()); 101 102 return NS_OK; 103 } 104 105 nsresult ScriptDecoder::DecodeRawData(JS::loader::ScriptLoadRequest* aRequest, 106 const uint8_t* aData, 107 uint32_t aDataLength, bool aEndOfStream) { 108 if (aRequest->IsUTF16Text()) { 109 return DecodeRawDataHelper<char16_t>(aRequest, aData, aDataLength, 110 aEndOfStream); 111 } 112 113 return DecodeRawDataHelper<Utf8Unit>(aRequest, aData, aDataLength, 114 aEndOfStream); 115 } 116 117 ScriptLoadHandler::ScriptLoadHandler( 118 ScriptLoader* aScriptLoader, JS::loader::ScriptLoadRequest* aRequest, 119 UniquePtr<SRICheckDataVerifier>&& aSRIDataVerifier) 120 : mScriptLoader(aScriptLoader), 121 mRequest(aRequest), 122 mSRIDataVerifier(std::move(aSRIDataVerifier)), 123 mSRIStatus(NS_OK) { 124 MOZ_ASSERT(aRequest->IsUnknownDataType()); 125 MOZ_ASSERT(aRequest->IsFetching()); 126 } 127 128 ScriptLoadHandler::~ScriptLoadHandler() = default; 129 130 NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver, 131 nsIChannelEventSink, nsIInterfaceRequestor) 132 133 NS_IMETHODIMP 134 ScriptLoadHandler::OnStartRequest(nsIRequest* aRequest) { 135 mRequest->SetMinimumExpirationTime( 136 nsContentUtils::GetSubresourceCacheExpirationTime(aRequest, 137 mRequest->URI())); 138 139 return NS_OK; 140 } 141 142 NS_IMETHODIMP 143 ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, 144 nsISupports* aContext, 145 uint32_t aDataLength, const uint8_t* aData, 146 uint32_t* aConsumedLength) { 147 nsCOMPtr<nsIRequest> channelRequest; 148 aLoader->GetRequest(getter_AddRefs(channelRequest)); 149 150 auto firstTime = !mPreloadStartNotified; 151 if (!mPreloadStartNotified) { 152 mPreloadStartNotified = true; 153 mRequest->GetScriptLoadContext()->NotifyStart(channelRequest); 154 } 155 156 if (mRequest->IsCanceled()) { 157 // If request cancelled, ignore any incoming data. 158 *aConsumedLength = aDataLength; 159 return NS_OK; 160 } 161 162 nsresult rv = NS_OK; 163 if (mRequest->IsUnknownDataType()) { 164 rv = EnsureKnownDataType(aLoader); 165 NS_ENSURE_SUCCESS(rv, rv); 166 } 167 168 if (mRequest->IsSerializedStencil() && firstTime) { 169 PerfStats::RecordMeasurementStart(PerfStats::Metric::JSBC_IO_Read); 170 } 171 172 if (mRequest->IsTextSource()) { 173 if (!EnsureDecoder(aLoader, aData, aDataLength, 174 /* aEndOfStream = */ false)) { 175 return NS_OK; 176 } 177 178 // Below we will/shall consume entire data chunk. 179 *aConsumedLength = aDataLength; 180 181 // Decoder has already been initialized. -- trying to decode all loaded 182 // bytes. 183 rv = mDecoder->DecodeRawData(mRequest, aData, aDataLength, 184 /* aEndOfStream = */ false); 185 NS_ENSURE_SUCCESS(rv, rv); 186 187 // If SRI is required for this load, appending new bytes to the hash. 188 if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) { 189 mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData); 190 } 191 } else { 192 MOZ_ASSERT(mRequest->IsSerializedStencil()); 193 if (!mRequest->SRIAndSerializedStencil().append(aData, aDataLength)) { 194 return NS_ERROR_OUT_OF_MEMORY; 195 } 196 197 *aConsumedLength = aDataLength; 198 uint32_t sriLength = 0; 199 rv = MaybeDecodeSRI(&sriLength); 200 if (NS_FAILED(rv)) { 201 return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest)); 202 } 203 if (sriLength) { 204 mRequest->SetSRILength(sriLength); 205 } 206 } 207 208 return rv; 209 } 210 211 bool ScriptLoadHandler::TrySetDecoder(nsIIncrementalStreamLoader* aLoader, 212 const uint8_t* aData, 213 uint32_t aDataLength, bool aEndOfStream) { 214 MOZ_ASSERT(mDecoder == nullptr, 215 "can't have a decoder already if we're trying to set one"); 216 217 // JavaScript modules are always UTF-8. 218 if (mRequest->IsModuleRequest()) { 219 mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING, 220 ScriptDecoder::BOMHandling::Remove); 221 return true; 222 } 223 224 // Determine if BOM check should be done. This occurs either 225 // if end-of-stream has been reached, or at least 3 bytes have 226 // been read from input. 227 if (!aEndOfStream && (aDataLength < 3)) { 228 return false; 229 } 230 231 // Do BOM detection. 232 const Encoding* encoding; 233 std::tie(encoding, std::ignore) = Encoding::ForBOM(Span(aData, aDataLength)); 234 if (encoding) { 235 mDecoder = 236 MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Remove); 237 return true; 238 } 239 240 // BOM detection failed, check content stream for charset. 241 nsCOMPtr<nsIRequest> req; 242 nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); 243 NS_ASSERTION(req, "StreamLoader's request went away prematurely"); 244 NS_ENSURE_SUCCESS(rv, false); 245 246 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req); 247 248 if (channel) { 249 nsAutoCString label; 250 if (NS_SUCCEEDED(channel->GetContentCharset(label)) && 251 (encoding = Encoding::ForLabel(label))) { 252 mDecoder = MakeUnique<ScriptDecoder>(encoding, 253 ScriptDecoder::BOMHandling::Ignore); 254 return true; 255 } 256 } 257 258 // Check the hint charset from the script element or preload 259 // request. 260 nsAutoString hintCharset; 261 if (!mRequest->GetScriptLoadContext()->IsPreload()) { 262 mRequest->GetScriptLoadContext()->GetHintCharset(hintCharset); 263 } else { 264 nsTArray<ScriptLoader::PreloadInfo>::index_type i = 265 mScriptLoader->mPreloads.IndexOf( 266 mRequest, 0, ScriptLoader::PreloadRequestComparator()); 267 268 NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex, 269 "Incorrect preload bookkeeping"); 270 hintCharset = mScriptLoader->mPreloads[i].mCharset; 271 } 272 273 if ((encoding = Encoding::ForLabel(hintCharset))) { 274 mDecoder = 275 MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Ignore); 276 return true; 277 } 278 279 // Get the charset from the charset of the document. 280 if (mScriptLoader->mDocument) { 281 encoding = mScriptLoader->mDocument->GetDocumentCharacterSet(); 282 mDecoder = 283 MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Ignore); 284 return true; 285 } 286 287 // Curiously, there are various callers that don't pass aDocument. The 288 // fallback in the old code was ISO-8859-1, which behaved like 289 // windows-1252. 290 mDecoder = MakeUnique<ScriptDecoder>(WINDOWS_1252_ENCODING, 291 ScriptDecoder::BOMHandling::Ignore); 292 return true; 293 } 294 295 nsresult ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength) { 296 *sriLength = 0; 297 298 if (!mSRIDataVerifier || mSRIDataVerifier->IsInvalid() || 299 mSRIDataVerifier->IsComplete() || NS_FAILED(mSRIStatus)) { 300 return NS_OK; 301 } 302 303 // Skip until the content is large enough to be decoded. 304 JS::TranscodeBuffer& receivedData = mRequest->SRIAndSerializedStencil(); 305 if (receivedData.length() <= mSRIDataVerifier->DataSummaryLength()) { 306 return NS_OK; 307 } 308 309 mSRIStatus = mSRIDataVerifier->ImportDataSummary(receivedData.length(), 310 receivedData.begin()); 311 312 if (NS_FAILED(mSRIStatus)) { 313 // We are unable to decode the hash contained in the alternate data which 314 // contains the serialized Stencil, or it does not use the same algorithm. 315 LOG( 316 ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart " 317 "request")); 318 return mSRIStatus; 319 } 320 321 *sriLength = mSRIDataVerifier->DataSummaryLength(); 322 MOZ_ASSERT(*sriLength > 0); 323 return NS_OK; 324 } 325 326 nsresult ScriptLoadHandler::EnsureKnownDataType( 327 nsIIncrementalStreamLoader* aLoader) { 328 MOZ_ASSERT(mRequest->IsUnknownDataType()); 329 MOZ_ASSERT(mRequest->IsFetching()); 330 331 nsCOMPtr<nsIRequest> req; 332 nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); 333 MOZ_ASSERT(req, "StreamLoader's request went away prematurely"); 334 NS_ENSURE_SUCCESS(rv, rv); 335 336 if (mRequest->mFetchSourceOnly) { 337 mRequest->SetTextSource(mRequest->mLoadContext.get()); 338 TRACE_FOR_TEST(mRequest, "load:source"); 339 return NS_OK; 340 } 341 342 nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req)); 343 if (cic) { 344 nsAutoCString altDataType; 345 cic->GetAlternativeDataType(altDataType); 346 if (altDataType.Equals(ScriptLoader::BytecodeMimeTypeFor(mRequest))) { 347 mRequest->SetSerializedStencil(); 348 TRACE_FOR_TEST(mRequest, "load:diskcache"); 349 return NS_OK; 350 } 351 MOZ_ASSERT(altDataType.IsEmpty()); 352 } 353 354 mRequest->SetTextSource(mRequest->mLoadContext.get()); 355 TRACE_FOR_TEST(mRequest, "load:source"); 356 357 MOZ_ASSERT(!mRequest->IsUnknownDataType()); 358 MOZ_ASSERT(mRequest->IsFetching()); 359 return NS_OK; 360 } 361 362 NS_IMETHODIMP 363 ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, 364 nsISupports* aContext, nsresult aStatus, 365 uint32_t aDataLength, 366 const uint8_t* aData) { 367 nsresult rv = NS_OK; 368 if (LOG_ENABLED()) { 369 nsAutoCString url; 370 mRequest->URI()->GetAsciiSpec(url); 371 LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest.get(), 372 url.get())); 373 } 374 375 nsCOMPtr<nsIRequest> channelRequest; 376 aLoader->GetRequest(getter_AddRefs(channelRequest)); 377 378 mRequest->mNetworkMetadata = 379 new SubResourceNetworkMetadataHolder(channelRequest); 380 381 { 382 nsCOMPtr<nsIChannel> channel = do_QueryInterface(channelRequest); 383 channel->SetNotificationCallbacks(nullptr); 384 } 385 386 auto firstMessage = !mPreloadStartNotified; 387 if (!mPreloadStartNotified) { 388 mPreloadStartNotified = true; 389 mRequest->GetScriptLoadContext()->NotifyStart(channelRequest); 390 } 391 392 auto notifyStop = MakeScopeExit([&] { 393 mRequest->GetScriptLoadContext()->NotifyStop(channelRequest, rv); 394 }); 395 396 if (!mRequest->IsCanceled()) { 397 if (mRequest->IsUnknownDataType()) { 398 rv = EnsureKnownDataType(aLoader); 399 NS_ENSURE_SUCCESS(rv, rv); 400 } 401 402 if (mRequest->IsSerializedStencil() && !firstMessage) { 403 // if firstMessage, then entire stream is in aData, and PerfStats would 404 // measure 0 time 405 PerfStats::RecordMeasurementEnd(PerfStats::Metric::JSBC_IO_Read); 406 } 407 408 if (mRequest->IsTextSource()) { 409 DebugOnly<bool> encoderSet = 410 EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true); 411 MOZ_ASSERT(encoderSet); 412 rv = mDecoder->DecodeRawData(mRequest, aData, aDataLength, 413 /* aEndOfStream = */ true); 414 NS_ENSURE_SUCCESS(rv, rv); 415 416 LOG(("ScriptLoadRequest (%p): Source length in code units = %u", 417 mRequest.get(), unsigned(mRequest->ScriptTextLength()))); 418 419 // If SRI is required for this load, appending new bytes to the hash. 420 if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) { 421 mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData); 422 } 423 } else { 424 MOZ_ASSERT(mRequest->IsSerializedStencil()); 425 JS::TranscodeBuffer& buf = mRequest->SRIAndSerializedStencil(); 426 if (!buf.append(aData, aDataLength)) { 427 return NS_ERROR_OUT_OF_MEMORY; 428 } 429 430 LOG(("ScriptLoadRequest (%p): SRIAndSerializedStencil length = %u", 431 mRequest.get(), unsigned(buf.length()))); 432 433 // If we abort while decoding the SRI, we fallback on explicitly 434 // requesting the source. Thus, we should not continue in 435 // ScriptLoader::OnStreamComplete, which removes the request from the 436 // waiting lists. 437 // 438 // We calculate the SRI length below. 439 uint32_t unused; 440 rv = MaybeDecodeSRI(&unused); 441 if (NS_FAILED(rv)) { 442 return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest)); 443 } 444 445 // The serialized stencil always starts with the SRI hash, thus even if 446 // there is no SRI data verifier instance, we still want to skip the hash. 447 uint32_t sriLength; 448 rv = SRICheckDataVerifier::DataSummaryLength(buf.length(), buf.begin(), 449 &sriLength); 450 if (NS_FAILED(rv)) { 451 return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest)); 452 } 453 454 mRequest->SetSRILength(sriLength); 455 456 Vector<uint8_t> compressed; 457 // mRequest has the compressed data, but will be filled with the 458 // uncompressed data 459 compressed.swap(buf); 460 if (!JS::loader::ScriptBytecodeDecompress( 461 compressed, mRequest->GetSRILength(), buf)) { 462 return NS_ERROR_UNEXPECTED; 463 } 464 } 465 } 466 467 // Everything went well, keep the CacheInfoChannel alive such that we can 468 // later save the serialized stencil on the cache entry. 469 // we have to mediate and use mRequest. 470 rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus, 471 mSRIDataVerifier.get()); 472 473 return rv; 474 } 475 476 NS_IMETHODIMP 477 ScriptLoadHandler::GetInterface(const nsIID& aIID, void** aResult) { 478 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { 479 return QueryInterface(aIID, aResult); 480 } 481 482 return NS_NOINTERFACE; 483 } 484 485 nsresult ScriptLoadHandler::AsyncOnChannelRedirect( 486 nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags, 487 nsIAsyncVerifyRedirectCallback* aCallback) { 488 mRequest->SetMinimumExpirationTime( 489 nsContentUtils::GetSubresourceCacheExpirationTime(aOld, mRequest->URI())); 490 491 aCallback->OnRedirectVerifyCallback(NS_OK); 492 493 return NS_OK; 494 } 495 496 #undef LOG_ENABLED 497 #undef LOG 498 499 } // namespace mozilla::dom