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/StaticPrefs_dom.h"
25 #include "mozilla/Utf8.h"
26 #include "mozilla/Vector.h"
27 #include "mozilla/dom/Document.h"
28 #include "mozilla/dom/SRICheck.h"
29 #include "mozilla/dom/ScriptDecoding.h"
31 #include "nsContentUtils.h"
33 #include "nsICacheInfoChannel.h"
34 #include "nsIChannel.h"
35 #include "nsIHttpChannel.h"
36 #include "nsIRequest.h"
37 #include "nsIScriptElement.h"
39 #include "nsJSUtils.h"
40 #include "nsMimeTypes.h"
45 namespace mozilla::dom
{
49 MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
51 #define LOG_ENABLED() \
52 MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
54 ScriptDecoder::ScriptDecoder(const Encoding
* aEncoding
,
55 ScriptDecoder::BOMHandling handleBOM
) {
56 if (handleBOM
== BOMHandling::Ignore
) {
57 mDecoder
= aEncoding
->NewDecoderWithoutBOMHandling();
59 mDecoder
= aEncoding
->NewDecoderWithBOMRemoval();
64 template <typename Unit
>
65 nsresult
ScriptDecoder::DecodeRawDataHelper(
66 JS::loader::ScriptLoadRequest
* aRequest
, const uint8_t* aData
,
67 uint32_t aDataLength
, bool aEndOfStream
) {
68 CheckedInt
<size_t> needed
=
69 ScriptDecoding
<Unit
>::MaxBufferLength(mDecoder
, aDataLength
);
70 if (!needed
.isValid()) {
71 return NS_ERROR_OUT_OF_MEMORY
;
74 // Reference to the script source buffer which we will update.
75 JS::loader::ScriptLoadRequest::ScriptTextBuffer
<Unit
>& scriptText
=
76 aRequest
->ScriptText
<Unit
>();
78 uint32_t haveRead
= scriptText
.length();
80 CheckedInt
<uint32_t> capacity
= haveRead
;
81 capacity
+= needed
.value();
83 if (!capacity
.isValid() || !scriptText
.resize(capacity
.value())) {
84 return NS_ERROR_OUT_OF_MEMORY
;
87 size_t written
= ScriptDecoding
<Unit
>::DecodeInto(
88 mDecoder
, Span(aData
, aDataLength
),
89 Span(scriptText
.begin() + haveRead
, needed
.value()), aEndOfStream
);
90 MOZ_ASSERT(written
<= needed
.value());
93 MOZ_ASSERT(haveRead
<= capacity
.value(),
94 "mDecoder produced more data than expected");
95 MOZ_ALWAYS_TRUE(scriptText
.resize(haveRead
));
96 aRequest
->SetReceivedScriptTextLength(scriptText
.length());
101 nsresult
ScriptDecoder::DecodeRawData(JS::loader::ScriptLoadRequest
* aRequest
,
102 const uint8_t* aData
,
103 uint32_t aDataLength
, bool aEndOfStream
) {
104 if (aRequest
->IsUTF16Text()) {
105 return DecodeRawDataHelper
<char16_t
>(aRequest
, aData
, aDataLength
,
109 return DecodeRawDataHelper
<Utf8Unit
>(aRequest
, aData
, aDataLength
,
113 ScriptLoadHandler::ScriptLoadHandler(
114 ScriptLoader
* aScriptLoader
, JS::loader::ScriptLoadRequest
* aRequest
,
115 UniquePtr
<SRICheckDataVerifier
>&& aSRIDataVerifier
)
116 : mScriptLoader(aScriptLoader
),
118 mSRIDataVerifier(std::move(aSRIDataVerifier
)),
120 MOZ_ASSERT(aRequest
->IsUnknownDataType());
121 MOZ_ASSERT(aRequest
->IsFetching());
124 ScriptLoadHandler::~ScriptLoadHandler() = default;
126 NS_IMPL_ISUPPORTS(ScriptLoadHandler
, nsIIncrementalStreamLoaderObserver
)
129 ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader
* aLoader
,
130 nsISupports
* aContext
,
131 uint32_t aDataLength
, const uint8_t* aData
,
132 uint32_t* aConsumedLength
) {
133 nsCOMPtr
<nsIRequest
> channelRequest
;
134 aLoader
->GetRequest(getter_AddRefs(channelRequest
));
136 auto firstTime
= !mPreloadStartNotified
;
137 if (!mPreloadStartNotified
) {
138 mPreloadStartNotified
= true;
139 mRequest
->GetScriptLoadContext()->NotifyStart(channelRequest
);
142 if (mRequest
->IsCanceled()) {
143 // If request cancelled, ignore any incoming data.
144 *aConsumedLength
= aDataLength
;
149 if (mRequest
->IsUnknownDataType()) {
150 rv
= EnsureKnownDataType(aLoader
);
151 NS_ENSURE_SUCCESS(rv
, rv
);
154 if (mRequest
->IsBytecode() && firstTime
) {
155 PerfStats::RecordMeasurementStart(PerfStats::Metric::JSBC_IO_Read
);
158 if (mRequest
->IsTextSource()) {
159 if (!EnsureDecoder(aLoader
, aData
, aDataLength
,
160 /* aEndOfStream = */ false)) {
164 // Below we will/shall consume entire data chunk.
165 *aConsumedLength
= aDataLength
;
167 // Decoder has already been initialized. -- trying to decode all loaded
169 rv
= mDecoder
->DecodeRawData(mRequest
, aData
, aDataLength
,
170 /* aEndOfStream = */ false);
171 NS_ENSURE_SUCCESS(rv
, rv
);
173 // If SRI is required for this load, appending new bytes to the hash.
174 if (mSRIDataVerifier
&& NS_SUCCEEDED(mSRIStatus
)) {
175 mSRIStatus
= mSRIDataVerifier
->Update(aDataLength
, aData
);
178 MOZ_ASSERT(mRequest
->IsBytecode());
179 if (!mRequest
->SRIAndBytecode().append(aData
, aDataLength
)) {
180 return NS_ERROR_OUT_OF_MEMORY
;
183 *aConsumedLength
= aDataLength
;
184 uint32_t sriLength
= 0;
185 rv
= MaybeDecodeSRI(&sriLength
);
187 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
190 mRequest
->SetSRILength(sriLength
);
197 bool ScriptLoadHandler::TrySetDecoder(nsIIncrementalStreamLoader
* aLoader
,
198 const uint8_t* aData
,
199 uint32_t aDataLength
, bool aEndOfStream
) {
200 MOZ_ASSERT(mDecoder
== nullptr,
201 "can't have a decoder already if we're trying to set one");
203 // JavaScript modules are always UTF-8.
204 if (mRequest
->IsModuleRequest()) {
205 mDecoder
= MakeUnique
<ScriptDecoder
>(UTF_8_ENCODING
,
206 ScriptDecoder::BOMHandling::Remove
);
210 // Determine if BOM check should be done. This occurs either
211 // if end-of-stream has been reached, or at least 3 bytes have
212 // been read from input.
213 if (!aEndOfStream
&& (aDataLength
< 3)) {
218 const Encoding
* encoding
;
219 std::tie(encoding
, std::ignore
) = Encoding::ForBOM(Span(aData
, aDataLength
));
222 MakeUnique
<ScriptDecoder
>(encoding
, ScriptDecoder::BOMHandling::Remove
);
226 // BOM detection failed, check content stream for charset.
227 nsCOMPtr
<nsIRequest
> req
;
228 nsresult rv
= aLoader
->GetRequest(getter_AddRefs(req
));
229 NS_ASSERTION(req
, "StreamLoader's request went away prematurely");
230 NS_ENSURE_SUCCESS(rv
, false);
232 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(req
);
236 if (NS_SUCCEEDED(channel
->GetContentCharset(label
)) &&
237 (encoding
= Encoding::ForLabel(label
))) {
238 mDecoder
= MakeUnique
<ScriptDecoder
>(encoding
,
239 ScriptDecoder::BOMHandling::Ignore
);
244 // Check the hint charset from the script element or preload
246 nsAutoString hintCharset
;
247 if (!mRequest
->GetScriptLoadContext()->IsPreload()) {
248 mRequest
->GetScriptLoadContext()->GetScriptElement()->GetScriptCharset(
251 nsTArray
<ScriptLoader::PreloadInfo
>::index_type i
=
252 mScriptLoader
->mPreloads
.IndexOf(
253 mRequest
, 0, ScriptLoader::PreloadRequestComparator());
255 NS_ASSERTION(i
!= mScriptLoader
->mPreloads
.NoIndex
,
256 "Incorrect preload bookkeeping");
257 hintCharset
= mScriptLoader
->mPreloads
[i
].mCharset
;
260 if ((encoding
= Encoding::ForLabel(hintCharset
))) {
262 MakeUnique
<ScriptDecoder
>(encoding
, ScriptDecoder::BOMHandling::Ignore
);
266 // Get the charset from the charset of the document.
267 if (mScriptLoader
->mDocument
) {
268 encoding
= mScriptLoader
->mDocument
->GetDocumentCharacterSet();
270 MakeUnique
<ScriptDecoder
>(encoding
, ScriptDecoder::BOMHandling::Ignore
);
274 // Curiously, there are various callers that don't pass aDocument. The
275 // fallback in the old code was ISO-8859-1, which behaved like
277 mDecoder
= MakeUnique
<ScriptDecoder
>(WINDOWS_1252_ENCODING
,
278 ScriptDecoder::BOMHandling::Ignore
);
282 nsresult
ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength
) {
285 if (!mSRIDataVerifier
|| mSRIDataVerifier
->IsComplete() ||
286 NS_FAILED(mSRIStatus
)) {
290 // Skip until the content is large enough to be decoded.
291 JS::TranscodeBuffer
& receivedData
= mRequest
->SRIAndBytecode();
292 if (receivedData
.length() <= mSRIDataVerifier
->DataSummaryLength()) {
296 mSRIStatus
= mSRIDataVerifier
->ImportDataSummary(receivedData
.length(),
297 receivedData
.begin());
299 if (NS_FAILED(mSRIStatus
)) {
300 // We are unable to decode the hash contained in the alternate data which
301 // contains the bytecode, or it does not use the same algorithm.
303 ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
308 *sriLength
= mSRIDataVerifier
->DataSummaryLength();
309 MOZ_ASSERT(*sriLength
> 0);
313 nsresult
ScriptLoadHandler::EnsureKnownDataType(
314 nsIIncrementalStreamLoader
* aLoader
) {
315 MOZ_ASSERT(mRequest
->IsUnknownDataType());
316 MOZ_ASSERT(mRequest
->IsFetching());
318 nsCOMPtr
<nsIRequest
> req
;
319 nsresult rv
= aLoader
->GetRequest(getter_AddRefs(req
));
320 MOZ_ASSERT(req
, "StreamLoader's request went away prematurely");
321 NS_ENSURE_SUCCESS(rv
, rv
);
323 if (mRequest
->mFetchSourceOnly
) {
324 mRequest
->SetTextSource(mRequest
->mLoadContext
.get());
325 TRACE_FOR_TEST(mRequest
->GetScriptLoadContext()->GetScriptElement(),
326 "scriptloader_load_source");
330 nsCOMPtr
<nsICacheInfoChannel
> cic(do_QueryInterface(req
));
332 nsAutoCString altDataType
;
333 cic
->GetAlternativeDataType(altDataType
);
334 if (altDataType
.Equals(ScriptLoader::BytecodeMimeTypeFor(mRequest
))) {
335 mRequest
->SetBytecode();
336 TRACE_FOR_TEST(mRequest
->GetScriptLoadContext()->GetScriptElement(),
337 "scriptloader_load_bytecode");
340 MOZ_ASSERT(altDataType
.IsEmpty());
343 mRequest
->SetTextSource(mRequest
->mLoadContext
.get());
344 TRACE_FOR_TEST(mRequest
->GetScriptLoadContext()->GetScriptElement(),
345 "scriptloader_load_source");
347 MOZ_ASSERT(!mRequest
->IsUnknownDataType());
348 MOZ_ASSERT(mRequest
->IsFetching());
353 ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader
* aLoader
,
354 nsISupports
* aContext
, nsresult aStatus
,
355 uint32_t aDataLength
,
356 const uint8_t* aData
) {
360 mRequest
->mURI
->GetAsciiSpec(url
);
361 LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest
.get(),
365 nsCOMPtr
<nsIRequest
> channelRequest
;
366 aLoader
->GetRequest(getter_AddRefs(channelRequest
));
368 auto firstMessage
= !mPreloadStartNotified
;
369 if (!mPreloadStartNotified
) {
370 mPreloadStartNotified
= true;
371 mRequest
->GetScriptLoadContext()->NotifyStart(channelRequest
);
374 auto notifyStop
= MakeScopeExit([&] {
375 mRequest
->GetScriptLoadContext()->NotifyStop(channelRequest
, rv
);
378 if (!mRequest
->IsCanceled()) {
379 if (mRequest
->IsUnknownDataType()) {
380 rv
= EnsureKnownDataType(aLoader
);
381 NS_ENSURE_SUCCESS(rv
, rv
);
384 if (mRequest
->IsBytecode() && !firstMessage
) {
385 // if firstMessage, then entire stream is in aData, and PerfStats would
387 PerfStats::RecordMeasurementEnd(PerfStats::Metric::JSBC_IO_Read
);
390 if (mRequest
->IsTextSource()) {
391 DebugOnly
<bool> encoderSet
=
392 EnsureDecoder(aLoader
, aData
, aDataLength
, /* aEndOfStream = */ true);
393 MOZ_ASSERT(encoderSet
);
394 rv
= mDecoder
->DecodeRawData(mRequest
, aData
, aDataLength
,
395 /* aEndOfStream = */ true);
396 NS_ENSURE_SUCCESS(rv
, rv
);
398 LOG(("ScriptLoadRequest (%p): Source length in code units = %u",
399 mRequest
.get(), unsigned(mRequest
->ScriptTextLength())));
401 // If SRI is required for this load, appending new bytes to the hash.
402 if (mSRIDataVerifier
&& NS_SUCCEEDED(mSRIStatus
)) {
403 mSRIStatus
= mSRIDataVerifier
->Update(aDataLength
, aData
);
406 MOZ_ASSERT(mRequest
->IsBytecode());
407 JS::TranscodeBuffer
& bytecode
= mRequest
->SRIAndBytecode();
408 if (!bytecode
.append(aData
, aDataLength
)) {
409 return NS_ERROR_OUT_OF_MEMORY
;
412 LOG(("ScriptLoadRequest (%p): Bytecode length = %u", mRequest
.get(),
413 unsigned(bytecode
.length())));
415 // If we abort while decoding the SRI, we fallback on explictly requesting
416 // the source. Thus, we should not continue in
417 // ScriptLoader::OnStreamComplete, which removes the request from the
420 // We calculate the SRI length below.
422 rv
= MaybeDecodeSRI(&unused
);
424 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
427 // The bytecode cache always starts with the SRI hash, thus even if there
428 // is no SRI data verifier instance, we still want to skip the hash.
430 rv
= SRICheckDataVerifier::DataSummaryLength(
431 bytecode
.length(), bytecode
.begin(), &sriLength
);
433 return channelRequest
->Cancel(mScriptLoader
->RestartLoad(mRequest
));
436 mRequest
->SetSRILength(sriLength
);
438 Vector
<uint8_t> compressedBytecode
;
439 // mRequest has the compressed bytecode, but will be filled with the
440 // uncompressed bytecode
441 compressedBytecode
.swap(bytecode
);
442 if (!JS::loader::ScriptBytecodeDecompress(
443 compressedBytecode
, mRequest
->GetSRILength(), bytecode
)) {
444 return NS_ERROR_UNEXPECTED
;
449 // Everything went well, keep the CacheInfoChannel alive such that we can
450 // later save the bytecode on the cache entry.
451 if (NS_SUCCEEDED(rv
) && mRequest
->IsSource() &&
452 StaticPrefs::dom_script_loader_bytecode_cache_enabled()) {
453 mRequest
->mCacheInfo
= do_QueryInterface(channelRequest
);
454 LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p", mRequest
.get(),
455 mRequest
->mCacheInfo
.get()));
458 // we have to mediate and use mRequest.
459 rv
= mScriptLoader
->OnStreamComplete(aLoader
, mRequest
, aStatus
, mSRIStatus
,
460 mSRIDataVerifier
.get());
462 // In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
464 mRequest
->mCacheInfo
= nullptr;
473 } // namespace mozilla::dom