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"
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"
33 #include "nsContentUtils.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"
42 #include "nsJSUtils.h"
43 #include "nsMimeTypes.h"
48 namespace mozilla::dom
{
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();
62 mDecoder
= aEncoding
->NewDecoderWithBOMRemoval();
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());
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());
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
,
112 return DecodeRawDataHelper
<Utf8Unit
>(aRequest
, aData
, aDataLength
,
116 ScriptLoadHandler::ScriptLoadHandler(
117 ScriptLoader
* aScriptLoader
, JS::loader::ScriptLoadRequest
* aRequest
,
118 UniquePtr
<SRICheckDataVerifier
>&& aSRIDataVerifier
)
119 : mScriptLoader(aScriptLoader
),
121 mSRIDataVerifier(std::move(aSRIDataVerifier
)),
123 MOZ_ASSERT(aRequest
->IsUnknownDataType());
124 MOZ_ASSERT(aRequest
->IsFetching());
127 ScriptLoadHandler::~ScriptLoadHandler() = default;
129 NS_IMPL_ISUPPORTS(ScriptLoadHandler
, nsIIncrementalStreamLoaderObserver
,
130 nsIChannelEventSink
, nsIInterfaceRequestor
)
133 ScriptLoadHandler::OnStartRequest(nsIRequest
* aRequest
) {
134 mRequest
->SetMinimumExpirationTime(
135 nsContentUtils::GetSubresourceCacheExpirationTime(aRequest
,
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
;
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)) {
177 // Below we will/shall consume entire data chunk.
178 *aConsumedLength
= aDataLength
;
180 // Decoder has already been initialized. -- trying to decode all loaded
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
);
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
);
200 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
203 mRequest
->SetSRILength(sriLength
);
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
);
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)) {
231 const Encoding
* encoding
;
232 std::tie(encoding
, std::ignore
) = Encoding::ForBOM(Span(aData
, aDataLength
));
235 MakeUnique
<ScriptDecoder
>(encoding
, ScriptDecoder::BOMHandling::Remove
);
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
);
249 if (NS_SUCCEEDED(channel
->GetContentCharset(label
)) &&
250 (encoding
= Encoding::ForLabel(label
))) {
251 mDecoder
= MakeUnique
<ScriptDecoder
>(encoding
,
252 ScriptDecoder::BOMHandling::Ignore
);
257 // Check the hint charset from the script element or preload
259 nsAutoString hintCharset
;
260 if (!mRequest
->GetScriptLoadContext()->IsPreload()) {
261 mRequest
->GetScriptLoadContext()->GetHintCharset(hintCharset
);
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
))) {
274 MakeUnique
<ScriptDecoder
>(encoding
, ScriptDecoder::BOMHandling::Ignore
);
278 // Get the charset from the charset of the document.
279 if (mScriptLoader
->mDocument
) {
280 encoding
= mScriptLoader
->mDocument
->GetDocumentCharacterSet();
282 MakeUnique
<ScriptDecoder
>(encoding
, ScriptDecoder::BOMHandling::Ignore
);
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
289 mDecoder
= MakeUnique
<ScriptDecoder
>(WINDOWS_1252_ENCODING
,
290 ScriptDecoder::BOMHandling::Ignore
);
294 nsresult
ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength
) {
297 if (!mSRIDataVerifier
|| mSRIDataVerifier
->IsComplete() ||
298 NS_FAILED(mSRIStatus
)) {
302 // Skip until the content is large enough to be decoded.
303 JS::TranscodeBuffer
& receivedData
= mRequest
->SRIAndBytecode();
304 if (receivedData
.length() <= mSRIDataVerifier
->DataSummaryLength()) {
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.
315 ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
320 *sriLength
= mSRIDataVerifier
->DataSummaryLength();
321 MOZ_ASSERT(*sriLength
> 0);
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");
341 nsCOMPtr
<nsICacheInfoChannel
> cic(do_QueryInterface(req
));
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");
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());
362 ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader
* aLoader
,
363 nsISupports
* aContext
, nsresult aStatus
,
364 uint32_t aDataLength
,
365 const uint8_t* aData
) {
369 mRequest
->mURI
->GetAsciiSpec(url
);
370 LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest
.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
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
);
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
437 // We calculate the SRI length below.
439 rv
= MaybeDecodeSRI(&unused
);
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.
447 rv
= SRICheckDataVerifier::DataSummaryLength(
448 bytecode
.length(), bytecode
.begin(), &sriLength
);
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.
481 mRequest
->mCacheInfo
= nullptr;
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
);
510 } // namespace mozilla::dom