Bug 1886946: Remove incorrect assertion that buffer is not-pinned. r=sfink
[gecko.git] / dom / script / ScriptLoadHandler.cpp
blob08210c0c21a635465928ec3b55a692b578c163bc
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/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"
30 #include "nsCOMPtr.h"
31 #include "nsContentUtils.h"
32 #include "nsDebug.h"
33 #include "nsICacheInfoChannel.h"
34 #include "nsIChannel.h"
35 #include "nsIHttpChannel.h"
36 #include "nsIRequest.h"
37 #include "nsIScriptElement.h"
38 #include "nsIURI.h"
39 #include "nsJSUtils.h"
40 #include "nsMimeTypes.h"
41 #include "nsString.h"
42 #include "nsTArray.h"
43 #include "zlib.h"
45 namespace mozilla::dom {
47 #undef LOG
48 #define LOG(args) \
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();
58 } else {
59 mDecoder = aEncoding->NewDecoderWithBOMRemoval();
61 MOZ_ASSERT(mDecoder);
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());
92 haveRead += written;
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());
98 return NS_OK;
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,
106 aEndOfStream);
109 return DecodeRawDataHelper<Utf8Unit>(aRequest, aData, aDataLength,
110 aEndOfStream);
113 ScriptLoadHandler::ScriptLoadHandler(
114 ScriptLoader* aScriptLoader, JS::loader::ScriptLoadRequest* aRequest,
115 UniquePtr<SRICheckDataVerifier>&& aSRIDataVerifier)
116 : mScriptLoader(aScriptLoader),
117 mRequest(aRequest),
118 mSRIDataVerifier(std::move(aSRIDataVerifier)),
119 mSRIStatus(NS_OK) {
120 MOZ_ASSERT(aRequest->IsUnknownDataType());
121 MOZ_ASSERT(aRequest->IsFetching());
124 ScriptLoadHandler::~ScriptLoadHandler() = default;
126 NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
128 NS_IMETHODIMP
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;
145 return NS_OK;
148 nsresult rv = NS_OK;
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)) {
161 return NS_OK;
164 // Below we will/shall consume entire data chunk.
165 *aConsumedLength = aDataLength;
167 // Decoder has already been initialized. -- trying to decode all loaded
168 // bytes.
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);
177 } else {
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);
186 if (NS_FAILED(rv)) {
187 return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
189 if (sriLength) {
190 mRequest->SetSRILength(sriLength);
194 return rv;
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);
207 return true;
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)) {
214 return false;
217 // Do BOM detection.
218 const Encoding* encoding;
219 std::tie(encoding, std::ignore) = Encoding::ForBOM(Span(aData, aDataLength));
220 if (encoding) {
221 mDecoder =
222 MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Remove);
223 return true;
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);
234 if (channel) {
235 nsAutoCString label;
236 if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
237 (encoding = Encoding::ForLabel(label))) {
238 mDecoder = MakeUnique<ScriptDecoder>(encoding,
239 ScriptDecoder::BOMHandling::Ignore);
240 return true;
244 // Check the hint charset from the script element or preload
245 // request.
246 nsAutoString hintCharset;
247 if (!mRequest->GetScriptLoadContext()->IsPreload()) {
248 mRequest->GetScriptLoadContext()->GetScriptElement()->GetScriptCharset(
249 hintCharset);
250 } else {
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))) {
261 mDecoder =
262 MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Ignore);
263 return true;
266 // Get the charset from the charset of the document.
267 if (mScriptLoader->mDocument) {
268 encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
269 mDecoder =
270 MakeUnique<ScriptDecoder>(encoding, ScriptDecoder::BOMHandling::Ignore);
271 return true;
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
276 // windows-1252.
277 mDecoder = MakeUnique<ScriptDecoder>(WINDOWS_1252_ENCODING,
278 ScriptDecoder::BOMHandling::Ignore);
279 return true;
282 nsresult ScriptLoadHandler::MaybeDecodeSRI(uint32_t* sriLength) {
283 *sriLength = 0;
285 if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() ||
286 NS_FAILED(mSRIStatus)) {
287 return NS_OK;
290 // Skip until the content is large enough to be decoded.
291 JS::TranscodeBuffer& receivedData = mRequest->SRIAndBytecode();
292 if (receivedData.length() <= mSRIDataVerifier->DataSummaryLength()) {
293 return NS_OK;
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.
302 LOG(
303 ("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart "
304 "request"));
305 return mSRIStatus;
308 *sriLength = mSRIDataVerifier->DataSummaryLength();
309 MOZ_ASSERT(*sriLength > 0);
310 return NS_OK;
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");
327 return NS_OK;
330 nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
331 if (cic) {
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");
338 return NS_OK;
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());
349 return NS_OK;
352 NS_IMETHODIMP
353 ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
354 nsISupports* aContext, nsresult aStatus,
355 uint32_t aDataLength,
356 const uint8_t* aData) {
357 nsresult rv = NS_OK;
358 if (LOG_ENABLED()) {
359 nsAutoCString url;
360 mRequest->mURI->GetAsciiSpec(url);
361 LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)", mRequest.get(),
362 url.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
386 // measure 0 time
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);
405 } else {
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
418 // waiting lists.
420 // We calculate the SRI length below.
421 uint32_t unused;
422 rv = MaybeDecodeSRI(&unused);
423 if (NS_FAILED(rv)) {
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.
429 uint32_t sriLength;
430 rv = SRICheckDataVerifier::DataSummaryLength(
431 bytecode.length(), bytecode.begin(), &sriLength);
432 if (NS_FAILED(rv)) {
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.
463 if (NS_FAILED(rv)) {
464 mRequest->mCacheInfo = nullptr;
467 return rv;
470 #undef LOG_ENABLED
471 #undef LOG
473 } // namespace mozilla::dom