gecko-dev/dom/script/ScriptLoadHandler.cpp

443 строки
14 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ScriptLoadHandler.h"
#include "ScriptLoader.h"
#include "ScriptTrace.h"
#include "nsContentUtils.h"
#include "nsIEncodedChannel.h"
#include "nsIStringEnumerator.h"
#include "nsMimeTypes.h"
#include "mozilla/Telemetry.h"
namespace mozilla {
namespace dom {
#undef LOG
#define LOG(args) \
MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() \
MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
ScriptLoadHandler::ScriptLoadHandler(ScriptLoader* aScriptLoader,
ScriptLoadRequest* aRequest,
SRICheckDataVerifier* aSRIDataVerifier)
: mScriptLoader(aScriptLoader),
mRequest(aRequest),
mSRIDataVerifier(aSRIDataVerifier),
mSRIStatus(NS_OK),
mDecoder()
{
MOZ_ASSERT(mRequest->IsUnknownDataType());
MOZ_ASSERT(mRequest->IsLoading());
}
ScriptLoadHandler::~ScriptLoadHandler()
{}
NS_IMPL_ISUPPORTS(ScriptLoadHandler, nsIIncrementalStreamLoaderObserver)
NS_IMETHODIMP
ScriptLoadHandler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
nsISupports* aContext,
uint32_t aDataLength,
const uint8_t* aData,
uint32_t* aConsumedLength)
{
if (mRequest->IsCanceled()) {
// If request cancelled, ignore any incoming data.
*aConsumedLength = aDataLength;
return NS_OK;
}
nsresult rv = NS_OK;
if (mRequest->IsUnknownDataType()) {
rv = EnsureKnownDataType(aLoader);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mRequest->IsTextSource()) {
if (!EnsureDecoder(aLoader, aData, aDataLength,
/* aEndOfStream = */ false)) {
return NS_OK;
}
// Below we will/shall consume entire data chunk.
*aConsumedLength = aDataLength;
// Decoder has already been initialized. -- trying to decode all loaded bytes.
rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ false);
NS_ENSURE_SUCCESS(rv, rv);
// If SRI is required for this load, appending new bytes to the hash.
if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
}
} else if (mRequest->IsBinASTSource()) {
if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Below we will/shall consume entire data chunk.
*aConsumedLength = aDataLength;
// If SRI is required for this load, appending new bytes to the hash.
if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
}
} else {
MOZ_ASSERT(mRequest->IsBytecode());
if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aConsumedLength = aDataLength;
rv = MaybeDecodeSRI();
if (NS_FAILED(rv)) {
nsCOMPtr<nsIRequest> channelRequest;
aLoader->GetRequest(getter_AddRefs(channelRequest));
return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
}
}
return rv;
}
nsresult
ScriptLoadHandler::DecodeRawData(const uint8_t* aData,
uint32_t aDataLength,
bool aEndOfStream)
{
CheckedInt<size_t> needed = mDecoder->MaxUTF16BufferLength(aDataLength);
if (!needed.isValid()) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Reference to the script source buffer which we will update.
ScriptLoadRequest::ScriptTextBuffer& scriptText = mRequest->ScriptText();
uint32_t haveRead = scriptText.length();
CheckedInt<uint32_t> capacity = haveRead;
capacity += needed.value();
if (!capacity.isValid() || !scriptText.resize(capacity.value())) {
return NS_ERROR_OUT_OF_MEMORY;
}
uint32_t result;
size_t read;
size_t written;
bool hadErrors;
Tie(result, read, written, hadErrors) = mDecoder->DecodeToUTF16(
MakeSpan(aData, aDataLength),
MakeSpan(scriptText.begin() + haveRead, needed.value()),
aEndOfStream);
MOZ_ASSERT(result == kInputEmpty);
MOZ_ASSERT(read == aDataLength);
MOZ_ASSERT(written <= needed.value());
Unused << hadErrors;
haveRead += written;
MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
MOZ_ALWAYS_TRUE(scriptText.resize(haveRead));
mRequest->mScriptTextLength = scriptText.length();
return NS_OK;
}
bool
ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
const uint8_t* aData,
uint32_t aDataLength,
bool aEndOfStream)
{
// Check if decoder has already been created.
if (mDecoder) {
return true;
}
nsAutoCString charset;
if (!EnsureDecoder(aLoader, aData, aDataLength, aEndOfStream, charset)) {
return false;
}
return true;
}
bool
ScriptLoadHandler::EnsureDecoder(nsIIncrementalStreamLoader* aLoader,
const uint8_t* aData,
uint32_t aDataLength,
bool aEndOfStream,
nsCString& oCharset)
{
// JavaScript modules are always UTF-8.
if (mRequest->IsModuleRequest()) {
oCharset = "UTF-8";
mDecoder = UTF_8_ENCODING->NewDecoderWithBOMRemoval();
return true;
}
// Determine if BOM check should be done. This occurs either
// if end-of-stream has been reached, or at least 3 bytes have
// been read from input.
if (!aEndOfStream && (aDataLength < 3)) {
return false;
}
// Do BOM detection.
const Encoding* encoding;
size_t bomLength;
Tie(encoding, bomLength) = Encoding::ForBOM(MakeSpan(aData, aDataLength));
if (encoding) {
mDecoder = encoding->NewDecoderWithBOMRemoval();
encoding->Name(oCharset);
return true;
}
// BOM detection failed, check content stream for charset.
nsCOMPtr<nsIRequest> req;
nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
NS_ASSERTION(req, "StreamLoader's request went away prematurely");
NS_ENSURE_SUCCESS(rv, false);
nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
if (channel) {
nsAutoCString label;
if (NS_SUCCEEDED(channel->GetContentCharset(label)) &&
(encoding = Encoding::ForLabel(label))) {
mDecoder = encoding->NewDecoderWithoutBOMHandling();
encoding->Name(oCharset);
return true;
}
}
// Check the hint charset from the script element or preload
// request.
nsAutoString hintCharset;
if (!mRequest->IsPreload()) {
mRequest->Element()->GetScriptCharset(hintCharset);
} else {
nsTArray<ScriptLoader::PreloadInfo>::index_type i =
mScriptLoader->mPreloads.IndexOf(mRequest, 0,
ScriptLoader::PreloadRequestComparator());
NS_ASSERTION(i != mScriptLoader->mPreloads.NoIndex,
"Incorrect preload bookkeeping");
hintCharset = mScriptLoader->mPreloads[i].mCharset;
}
if ((encoding = Encoding::ForLabel(hintCharset))) {
mDecoder = encoding->NewDecoderWithoutBOMHandling();
encoding->Name(oCharset);
return true;
}
// Get the charset from the charset of the document.
if (mScriptLoader->mDocument) {
encoding = mScriptLoader->mDocument->GetDocumentCharacterSet();
mDecoder = encoding->NewDecoderWithoutBOMHandling();
encoding->Name(oCharset);
return true;
}
// Curiously, there are various callers that don't pass aDocument. The
// fallback in the old code was ISO-8859-1, which behaved like
// windows-1252.
oCharset = "windows-1252";
mDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
return true;
}
nsresult
ScriptLoadHandler::MaybeDecodeSRI()
{
if (!mSRIDataVerifier || mSRIDataVerifier->IsComplete() || NS_FAILED(mSRIStatus)) {
return NS_OK;
}
// Skip until the content is large enough to be decoded.
if (mRequest->mScriptBytecode.length() <= mSRIDataVerifier->DataSummaryLength()) {
return NS_OK;
}
mSRIStatus = mSRIDataVerifier->ImportDataSummary(
mRequest->mScriptBytecode.length(), mRequest->mScriptBytecode.begin());
if (NS_FAILED(mSRIStatus)) {
// We are unable to decode the hash contained in the alternate data which
// contains the bytecode, or it does not use the same algorithm.
LOG(("ScriptLoadHandler::MaybeDecodeSRI, failed to decode SRI, restart request"));
return mSRIStatus;
}
mRequest->mBytecodeOffset = mSRIDataVerifier->DataSummaryLength();
return NS_OK;
}
nsresult
ScriptLoadHandler::EnsureKnownDataType(nsIIncrementalStreamLoader* aLoader)
{
MOZ_ASSERT(mRequest->IsUnknownDataType());
MOZ_ASSERT(mRequest->IsLoading());
nsCOMPtr<nsIRequest> req;
nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
NS_ENSURE_SUCCESS(rv, rv);
if (ScriptLoader::BinASTEncodingEnabled()) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
if (httpChannel) {
nsAutoCString mimeType;
httpChannel->GetContentType(mimeType);
if (mimeType.LowerCaseEqualsASCII(APPLICATION_JAVASCRIPT_BINAST)) {
if (mRequest->ShouldAcceptBinASTEncoding()) {
mRequest->SetBinASTSource();
TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
return NS_OK;
} else {
return NS_ERROR_FAILURE;
}
}
}
}
if (mRequest->IsLoadingSource()) {
mRequest->SetTextSource();
TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
return NS_OK;
}
nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
if (cic) {
nsAutoCString altDataType;
cic->GetAlternativeDataType(altDataType);
if (altDataType.Equals(nsContentUtils::JSBytecodeMimeType())) {
mRequest->SetBytecode();
TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_bytecode");
} else {
MOZ_ASSERT(altDataType.IsEmpty());
mRequest->SetTextSource();
TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
}
} else {
mRequest->SetTextSource();
TRACE_FOR_TEST(mRequest->Element(), "scriptloader_load_source");
}
MOZ_ASSERT(!mRequest->IsUnknownDataType());
MOZ_ASSERT(mRequest->IsLoading());
return NS_OK;
}
NS_IMETHODIMP
ScriptLoadHandler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
nsISupports* aContext,
nsresult aStatus,
uint32_t aDataLength,
const uint8_t* aData)
{
nsresult rv = NS_OK;
if (LOG_ENABLED()) {
nsAutoCString url;
mRequest->mURI->GetAsciiSpec(url);
LOG(("ScriptLoadRequest (%p): Stream complete (url = %s)",
mRequest.get(), url.get()));
}
nsCOMPtr<nsIRequest> channelRequest;
aLoader->GetRequest(getter_AddRefs(channelRequest));
if (!mRequest->IsCanceled()) {
if (mRequest->IsUnknownDataType()) {
rv = EnsureKnownDataType(aLoader);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mRequest->IsTextSource()) {
DebugOnly<bool> encoderSet =
EnsureDecoder(aLoader, aData, aDataLength, /* aEndOfStream = */ true);
MOZ_ASSERT(encoderSet);
rv = DecodeRawData(aData, aDataLength, /* aEndOfStream = */ true);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("ScriptLoadRequest (%p): Source length = %u",
mRequest.get(), unsigned(mRequest->ScriptText().length())));
// If SRI is required for this load, appending new bytes to the hash.
if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
}
} else if (mRequest->IsBinASTSource()) {
if (!mRequest->ScriptBinASTData().append(aData, aDataLength)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// If SRI is required for this load, appending new bytes to the hash.
if (mSRIDataVerifier && NS_SUCCEEDED(mSRIStatus)) {
mSRIStatus = mSRIDataVerifier->Update(aDataLength, aData);
}
} else {
MOZ_ASSERT(mRequest->IsBytecode());
if (!mRequest->mScriptBytecode.append(aData, aDataLength)) {
return NS_ERROR_OUT_OF_MEMORY;
}
LOG(("ScriptLoadRequest (%p): Bytecode length = %u",
mRequest.get(), unsigned(mRequest->mScriptBytecode.length())));
// If we abort while decoding the SRI, we fallback on explictly requesting
// the source. Thus, we should not continue in
// ScriptLoader::OnStreamComplete, which removes the request from the
// waiting lists.
rv = MaybeDecodeSRI();
if (NS_FAILED(rv)) {
return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
}
// The bytecode cache always starts with the SRI hash, thus even if there
// is no SRI data verifier instance, we still want to skip the hash.
rv = SRICheckDataVerifier::DataSummaryLength(mRequest->mScriptBytecode.length(),
mRequest->mScriptBytecode.begin(),
&mRequest->mBytecodeOffset);
if (NS_FAILED(rv)) {
return channelRequest->Cancel(mScriptLoader->RestartLoad(mRequest));
}
}
}
// Everything went well, keep the CacheInfoChannel alive such that we can
// later save the bytecode on the cache entry.
if (NS_SUCCEEDED(rv) && mRequest->IsSource() &&
nsContentUtils::IsBytecodeCacheEnabled()) {
mRequest->mCacheInfo = do_QueryInterface(channelRequest);
LOG(("ScriptLoadRequest (%p): nsICacheInfoChannel = %p",
mRequest.get(), mRequest->mCacheInfo.get()));
}
// we have to mediate and use mRequest.
rv = mScriptLoader->OnStreamComplete(aLoader, mRequest, aStatus, mSRIStatus,
mSRIDataVerifier);
// In case of failure, clear the mCacheInfoChannel to avoid keeping it alive.
if (NS_FAILED(rv)) {
mRequest->mCacheInfo = nullptr;
}
return rv;
}
#undef LOG_ENABLED
#undef LOG
} // dom namespace
} // mozilla namespace