Backed out changeset a8abdd77a92c (bug 1362119)

This commit is contained in:
Carsten "Tomcat" Book
2017-05-08 09:54:08 +02:00
parent 6f191c4573
commit 33c4ddfe00
13 changed files with 1160 additions and 1372 deletions

View File

@@ -4,12 +4,11 @@
* 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/. */
/*
* A class that handles loading and evaluation of <script> elements.
*/
#include "ScriptLoader.h"
#include "ScriptLoadHandler.h"
#include "ScriptLoadRequest.h"
#include "ScriptTrace.h"
#include "ModuleLoadRequest.h"
#include "ModuleScript.h"
#include "prsystem.h"
#include "jsapi.h"
@@ -70,11 +69,19 @@ using JS::SourceBufferHolder;
namespace mozilla {
namespace dom {
LazyLogModule ScriptLoader::gCspPRLog("CSP");
LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
static LazyLogModule gCspPRLog("CSP");
static LazyLogModule gScriptLoaderLog("ScriptLoader");
#define LOG(args) \
#define LOG_VERBOSE(args) \
MOZ_LOG(gScriptLoaderLog, mozilla::LogLevel::Verbose, args)
#define LOG(args) \
MOZ_LOG(gScriptLoaderLog, mozilla::LogLevel::Debug, args)
#define LOG_WARN(args) \
MOZ_LOG(gScriptLoaderLog, mozilla::LogLevel::Warning, args)
#define LOG_ERROR(args) \
MOZ_LOG(gScriptLoaderLog, mozilla::LogLevel::Error, args)
#define LOG_ENABLED() MOZ_LOG_TEST(gScriptLoaderLog, mozilla::LogLevel::Debug)
// These are the Alternate Data MIME type used by the ScriptLoader to
// register and read bytecode out of the nsCacheInfoChannel.
@@ -82,6 +89,477 @@ static NS_NAMED_LITERAL_CSTRING(
kBytecodeMimeType, "javascript/moz-bytecode-" NS_STRINGIFY(MOZ_BUILDID));
static NS_NAMED_LITERAL_CSTRING(kNullMimeType, "javascript/null");
void
ImplCycleCollectionUnlink(ScriptLoadRequestList& aField);
void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
ScriptLoadRequestList& aField,
const char* aName,
uint32_t aFlags = 0);
// This macro is used to wrap a tracing mechanism which is scheduling events
// which are then used by the JavaScript code of test cases to track the code
// path to verify the optimizations are working as expected.
#define TRACE_FOR_TEST(elem, str) \
PR_BEGIN_MACRO \
nsresult rv = NS_OK; \
rv = TestingDispatchEvent(elem, NS_LITERAL_STRING(str)); \
NS_ENSURE_SUCCESS(rv, rv); \
PR_END_MACRO
#define TRACE_FOR_TEST_BOOL(elem, str) \
PR_BEGIN_MACRO \
nsresult rv = NS_OK; \
rv = TestingDispatchEvent(elem, NS_LITERAL_STRING(str)); \
NS_ENSURE_SUCCESS(rv, false); \
PR_END_MACRO
#define TRACE_FOR_TEST_NONE(elem, str) \
PR_BEGIN_MACRO \
TestingDispatchEvent(elem, NS_LITERAL_STRING(str)); \
PR_END_MACRO
static nsresult
TestingDispatchEvent(nsIScriptElement* aScriptElement,
const nsAString& aEventType)
{
static bool sExposeTestInterfaceEnabled = false;
static bool sExposeTestInterfacePrefCached = false;
if (!sExposeTestInterfacePrefCached) {
sExposeTestInterfacePrefCached = true;
Preferences::AddBoolVarCache(&sExposeTestInterfaceEnabled,
"dom.expose_test_interfaces",
false);
}
if (!sExposeTestInterfaceEnabled) {
return NS_OK;
}
nsCOMPtr<nsINode> target(do_QueryInterface(aScriptElement));
if (!target) {
return NS_OK;
}
RefPtr<AsyncEventDispatcher> dispatcher =
new AsyncEventDispatcher(target, aEventType, true, false);
return dispatcher->PostDOMEvent();
}
//////////////////////////////////////////////////////////////
// ScriptLoadRequest
//////////////////////////////////////////////////////////////
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoadRequest)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_CLASS(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheInfo)
tmp->DropBytecodeCacheReferences();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheInfo)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
ScriptLoadRequest::~ScriptLoadRequest()
{
// We should always clean up any off-thread script parsing resources.
MOZ_ASSERT(!mOffThreadToken);
// But play it safe in release builds and try to clean them up here
// as a fail safe.
MaybeCancelOffThreadScript();
if (mScript) {
DropBytecodeCacheReferences();
}
}
void
ScriptLoadRequest::SetReady()
{
MOZ_ASSERT(mProgress != Progress::Ready);
mProgress = Progress::Ready;
}
void
ScriptLoadRequest::Cancel()
{
MaybeCancelOffThreadScript();
mIsCanceled = true;
}
void
ScriptLoadRequest::MaybeCancelOffThreadScript()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mOffThreadToken) {
return;
}
JSContext* cx = danger::GetJSContext();
// Follow the same conditions as ScriptLoader::AttemptAsyncScriptCompile
if (IsModuleRequest()) {
JS::CancelOffThreadModule(cx, mOffThreadToken);
} else if (IsSource()) {
JS::CancelOffThreadScript(cx, mOffThreadToken);
} else {
MOZ_ASSERT(IsBytecode());
JS::CancelOffThreadScriptDecoder(cx, mOffThreadToken);
}
mOffThreadToken = nullptr;
}
//////////////////////////////////////////////////////////////
// ModuleLoadRequest
//////////////////////////////////////////////////////////////
// A load request for a module, created for every top level module script and
// every module import. Load request can share an ModuleScript if there are
// multiple imports of the same module.
class ModuleLoadRequest final : public ScriptLoadRequest
{
~ModuleLoadRequest() = default;
ModuleLoadRequest(const ModuleLoadRequest& aOther) = delete;
ModuleLoadRequest(ModuleLoadRequest&& aOther) = delete;
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
ModuleLoadRequest(nsIScriptElement* aElement,
uint32_t aVersion,
CORSMode aCORSMode,
const SRIMetadata& aIntegrity,
ScriptLoader* aLoader);
bool IsTopLevel() const {
return mIsTopLevel;
}
void SetReady() override;
void Cancel() override;
void ModuleLoaded();
void DependenciesLoaded();
void LoadFailed();
// Is this a request for a top level module script or an import?
bool mIsTopLevel;
// The base URL used for resolving relative module imports.
nsCOMPtr<nsIURI> mBaseURL;
// Pointer to the script loader, used to trigger actions when the module load
// finishes.
RefPtr<ScriptLoader> mLoader;
// The importing module, or nullptr for top level module scripts. Used to
// implement the ancestor list checked when fetching module dependencies.
RefPtr<ModuleLoadRequest> mParent;
// Set to a module script object after a successful load or nullptr on
// failure.
RefPtr<ModuleScript> mModuleScript;
// A promise that is completed on successful load of this module and all of
// its dependencies, indicating that the module is ready for instantiation and
// evaluation.
MozPromiseHolder<GenericPromise> mReady;
// Array of imported modules.
nsTArray<RefPtr<ModuleLoadRequest>> mImports;
};
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest)
NS_INTERFACE_MAP_END_INHERITING(ScriptLoadRequest)
NS_IMPL_CYCLE_COLLECTION_INHERITED(ModuleLoadRequest, ScriptLoadRequest,
mBaseURL,
mLoader,
mParent,
mModuleScript,
mImports)
NS_IMPL_ADDREF_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
NS_IMPL_RELEASE_INHERITED(ModuleLoadRequest, ScriptLoadRequest)
ModuleLoadRequest::ModuleLoadRequest(nsIScriptElement* aElement,
uint32_t aVersion,
CORSMode aCORSMode,
const SRIMetadata &aIntegrity,
ScriptLoader* aLoader)
: ScriptLoadRequest(ScriptKind::Module,
aElement,
aVersion,
aCORSMode,
aIntegrity),
mIsTopLevel(true),
mLoader(aLoader)
{}
inline ModuleLoadRequest*
ScriptLoadRequest::AsModuleRequest()
{
MOZ_ASSERT(IsModuleRequest());
return static_cast<ModuleLoadRequest*>(this);
}
void ModuleLoadRequest::Cancel()
{
ScriptLoadRequest::Cancel();
mModuleScript = nullptr;
mProgress = ScriptLoadRequest::Progress::Ready;
for (size_t i = 0; i < mImports.Length(); i++) {
mImports[i]->Cancel();
}
mReady.RejectIfExists(NS_ERROR_FAILURE, __func__);
}
void
ModuleLoadRequest::SetReady()
{
#ifdef DEBUG
for (size_t i = 0; i < mImports.Length(); i++) {
MOZ_ASSERT(mImports[i]->IsReadyToRun());
}
#endif
ScriptLoadRequest::SetReady();
mReady.ResolveIfExists(true, __func__);
}
void
ModuleLoadRequest::ModuleLoaded()
{
// A module that was found to be marked as fetching in the module map has now
// been loaded.
mModuleScript = mLoader->GetFetchedModule(mURI);
mLoader->StartFetchingModuleDependencies(this);
}
void
ModuleLoadRequest::DependenciesLoaded()
{
// The module and all of its dependencies have been successfully fetched and
// compiled.
if (!mLoader->InstantiateModuleTree(this)) {
LoadFailed();
return;
}
SetReady();
mLoader->ProcessLoadedModuleTree(this);
mLoader = nullptr;
mParent = nullptr;
}
void
ModuleLoadRequest::LoadFailed()
{
Cancel();
mLoader->ProcessLoadedModuleTree(this);
mLoader = nullptr;
mParent = nullptr;
}
//////////////////////////////////////////////////////////////
// ModuleScript
//////////////////////////////////////////////////////////////
// A single module script. May be used to satisfy multiple load requests.
class ModuleScript final : public nsISupports
{
enum InstantiationState {
Uninstantiated,
Instantiated,
Errored
};
RefPtr<ScriptLoader> mLoader;
nsCOMPtr<nsIURI> mBaseURL;
JS::Heap<JSObject*> mModuleRecord;
JS::Heap<JS::Value> mException;
InstantiationState mInstantiationState;
~ModuleScript();
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ModuleScript)
ModuleScript(ScriptLoader* aLoader,
nsIURI* aBaseURL,
JS::Handle<JSObject*> aModuleRecord);
ScriptLoader* Loader() const { return mLoader; }
JSObject* ModuleRecord() const { return mModuleRecord; }
JS::Value Exception() const { return mException; }
nsIURI* BaseURL() const { return mBaseURL; }
void SetInstantiationResult(JS::Handle<JS::Value> aMaybeException);
bool IsUninstantiated() const {
return mInstantiationState == Uninstantiated;
}
bool IsInstantiated() const {
return mInstantiationState == Instantiated;
}
bool InstantiationFailed() const {
return mInstantiationState == Errored;
}
void UnlinkModuleRecord();
};
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleScript)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ModuleScript)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoader)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBaseURL)
tmp->UnlinkModuleRecord();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ModuleScript)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoader)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ModuleScript)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mModuleRecord)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mException)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleScript)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleScript)
ModuleScript::ModuleScript(ScriptLoader *aLoader, nsIURI* aBaseURL,
JS::Handle<JSObject*> aModuleRecord)
: mLoader(aLoader),
mBaseURL(aBaseURL),
mModuleRecord(aModuleRecord),
mInstantiationState(Uninstantiated)
{
MOZ_ASSERT(mLoader);
MOZ_ASSERT(mBaseURL);
MOZ_ASSERT(mModuleRecord);
MOZ_ASSERT(mException.isUndefined());
// Make module's host defined field point to this module script object.
// This is cleared in the UnlinkModuleRecord().
JS::SetModuleHostDefinedField(mModuleRecord, JS::PrivateValue(this));
HoldJSObjects(this);
}
void
ModuleScript::UnlinkModuleRecord()
{
// Remove module's back reference to this object request if present.
if (mModuleRecord) {
MOZ_ASSERT(JS::GetModuleHostDefinedField(mModuleRecord).toPrivate() ==
this);
JS::SetModuleHostDefinedField(mModuleRecord, JS::UndefinedValue());
}
mModuleRecord = nullptr;
mException.setUndefined();
}
ModuleScript::~ModuleScript()
{
if (mModuleRecord) {
// The object may be destroyed without being unlinked first.
UnlinkModuleRecord();
}
DropJSObjects(this);
}
void
ModuleScript::SetInstantiationResult(JS::Handle<JS::Value> aMaybeException)
{
MOZ_ASSERT(mInstantiationState == Uninstantiated);
MOZ_ASSERT(mModuleRecord);
MOZ_ASSERT(mException.isUndefined());
if (aMaybeException.isUndefined()) {
mInstantiationState = Instantiated;
} else {
mModuleRecord = nullptr;
mException = aMaybeException;
mInstantiationState = Errored;
}
}
//////////////////////////////////////////////////////////////
// ScriptLoadRequestList
//////////////////////////////////////////////////////////////
ScriptLoadRequestList::~ScriptLoadRequestList()
{
Clear();
}
void
ScriptLoadRequestList::Clear()
{
while (!isEmpty()) {
RefPtr<ScriptLoadRequest> first = StealFirst();
first->Cancel();
// And just let it go out of scope and die.
}
}
#ifdef DEBUG
bool
ScriptLoadRequestList::Contains(ScriptLoadRequest* aElem) const
{
for (const ScriptLoadRequest* req = getFirst();
req; req = req->getNext()) {
if (req == aElem) {
return true;
}
}
return false;
}
#endif // DEBUG
inline void
ImplCycleCollectionUnlink(ScriptLoadRequestList& aField)
{
while (!aField.isEmpty()) {
RefPtr<ScriptLoadRequest> first = aField.StealFirst();
}
}
inline void
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
ScriptLoadRequestList& aField,
const char* aName,
uint32_t aFlags)
{
for (ScriptLoadRequest* request = aField.getFirst();
request; request = request->getNext())
{
CycleCollectionNoteChild(aCallback, request, aName, aFlags);
}
}
//////////////////////////////////////////////////////////////
// ScriptLoader::PreloadInfo
//////////////////////////////////////////////////////////////
@@ -804,8 +1282,8 @@ ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest)
return true;
}
/* static */ bool
ScriptLoader::IsBytecodeCacheEnabled()
static bool
IsBytecodeCacheEnabled()
{
static bool sExposeTestInterfaceEnabled = false;
static bool sExposeTestInterfacePrefCached = false;
@@ -2177,6 +2655,14 @@ ScriptLoader::EncodeRequestBytecode(JSContext* aCx, ScriptLoadRequest* aRequest)
TRACE_FOR_TEST_NONE(aRequest->mElement, "scriptloader_bytecode_saved");
}
void
ScriptLoadRequest::DropBytecodeCacheReferences()
{
mCacheInfo = nullptr;
mScript = nullptr;
DropJSObjects(this);
}
void
ScriptLoader::GiveUpBytecodeEncoding()
{
@@ -2917,11 +3403,378 @@ ScriptLoader::MaybeRemovedDeferRequests()
return false;
}
//////////////////////////////////////////////////////////////
// ScriptLoadHandler
//////////////////////////////////////////////////////////////
ScriptLoadHandler::ScriptLoadHandler(ScriptLoader *aScriptLoader,
ScriptLoadRequest *aRequest,
mozilla::dom::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->IsSource()) {
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 {
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)
{
int32_t srcLen = aDataLength;
const char* src = reinterpret_cast<const char *>(aData);
int32_t dstLen;
nsresult rv =
mDecoder->GetMaxLength(src, srcLen, &dstLen);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t haveRead = mRequest->mScriptText.length();
CheckedInt<uint32_t> capacity = haveRead;
capacity += dstLen;
if (!capacity.isValid() || !mRequest->mScriptText.reserve(capacity.value())) {
return NS_ERROR_OUT_OF_MEMORY;
}
rv = mDecoder->Convert(src,
&srcLen,
mRequest->mScriptText.begin() + haveRead,
&dstLen);
NS_ENSURE_SUCCESS(rv, rv);
haveRead += dstLen;
MOZ_ASSERT(haveRead <= capacity.value(), "mDecoder produced more data than expected");
MOZ_ALWAYS_TRUE(mRequest->mScriptText.resizeUninitialized(haveRead));
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;
}
if (charset.Length() == 0) {
charset = "?";
}
mozilla::Telemetry::Accumulate(mozilla::Telemetry::DOM_SCRIPT_SRC_ENCODING,
charset);
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 = EncodingUtils::DecoderForEncoding(oCharset);
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.
if (nsContentUtils::CheckForBOM(aData, aDataLength, oCharset)) {
mDecoder = EncodingUtils::DecoderForEncoding(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 &&
NS_SUCCEEDED(channel->GetContentCharset(oCharset)) &&
EncodingUtils::FindEncodingForLabel(oCharset, oCharset)) {
mDecoder = EncodingUtils::DecoderForEncoding(oCharset);
return true;
}
// Check the hint charset from the script element or preload
// request.
nsAutoString hintCharset;
if (!mRequest->IsPreload()) {
mRequest->mElement->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 (EncodingUtils::FindEncodingForLabel(hintCharset, oCharset)) {
mDecoder = EncodingUtils::DecoderForEncoding(oCharset);
return true;
}
// Get the charset from the charset of the document.
if (mScriptLoader->mDocument) {
oCharset = mScriptLoader->mDocument->GetDocumentCharacterSet();
mDecoder = EncodingUtils::DecoderForEncoding(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. Saying windows-1252 for clarity and for compliance
// with the Encoding Standard.
oCharset = "windows-1252";
mDecoder = EncodingUtils::DecoderForEncoding(oCharset);
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());
if (mRequest->IsLoadingSource()) {
mRequest->mDataType = ScriptLoadRequest::DataType::Source;
TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
return NS_OK;
}
nsCOMPtr<nsIRequest> req;
nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
MOZ_ASSERT(req, "StreamLoader's request went away prematurely");
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(req));
if (cic) {
nsAutoCString altDataType;
cic->GetAlternativeDataType(altDataType);
if (altDataType == kBytecodeMimeType) {
mRequest->mDataType = ScriptLoadRequest::DataType::Bytecode;
TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_bytecode");
} else {
mRequest->mDataType = ScriptLoadRequest::DataType::Source;
TRACE_FOR_TEST(mRequest->mElement, "scriptloader_load_source");
}
} else {
mRequest->mDataType = ScriptLoadRequest::DataType::Source;
TRACE_FOR_TEST(mRequest->mElement, "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->IsSource()) {
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->mScriptText.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 {
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() && 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 TRACE_FOR_TEST
#undef TRACE_FOR_TEST_BOOL
#undef TRACE_FOR_TEST_NONE
#undef LOG_ENABLED
#undef LOG_ERROR
#undef LOG_WARN
#undef LOG
#undef LOG_VERBOSE
} // dom namespace
} // mozilla namespace