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