/* 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 "LoadedScript.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/RefPtr.h" // RefPtr, mozilla::MakeRefPtr
#include "mozilla/Sprintf.h" // SprintfLiteral
#include "mozilla/UniquePtr.h" // mozilla::UniquePtr, mozilla::MakeUnique
#include "nsIURI.h" // nsIURI::GetSpecOrDefault
#include "mozilla/dom/ScriptLoadContext.h" // ScriptLoadContext
#include "jsfriendapi.h"
#include "js/Modules.h" // JS::{Get,Set}ModulePrivate
#include "js/experimental/JSStencil.h" // JS::SizeOfStencil
#include "LoadContextBase.h" // LoadContextBase
#include "nsIChannel.h" // nsIChannel
namespace JS::loader {
NS_IMPL_ISUPPORTS(ScriptFetchInfo, nsISupports);
ScriptFetchInfo::ScriptFetchInfo(ScriptKind aKind,
mozilla::dom::ReferrerPolicy aReferrerPolicy,
ScriptFetchOptions* aFetchOptions,
nsIURI* aURI)
: mKind(aKind),
mReferrerPolicy(aReferrerPolicy),
mFetchOptions(aFetchOptions),
mBaseURL(aURI) {
MOZ_ASSERT(mFetchOptions);
}
size_t ScriptFetchInfo::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf)
const {
return aMallocSizeOf(this) +
mFetchOptions->SizeOfIncludingThis(aMallocSizeOf);
}
static bool IsInternalURIScheme(nsIURI* uri) {
return uri->SchemeIs(
"moz-extension") || uri->SchemeIs(
"resource") ||
uri->SchemeIs(
"moz-src") || uri->SchemeIs(
"chrome");
}
void ScriptFetchInfo::SetBaseURLFromChannelAndOriginalURI(
nsIChannel* aChannel, nsIURI* aOriginalURI) {
// Fixup moz-extension: and resource: URIs, because the channel URI will
// point to file:, which won't be allowed to load.
if (aOriginalURI && IsInternalURIScheme(aOriginalURI)) {
mBaseURL = aOriginalURI;
}
else {
aChannel->GetURI(getter_AddRefs(mBaseURL));
}
}
void ScriptFetchInfo::AssociateWithScript(JSScript* aScript) {
// Verify that the rewritten URL is available when manipulating the referrer.
MOZ_ASSERT(mBaseURL);
// Set a JSScript's private value to point to this object. The JS engine will
// increment our reference count by calling
// HostAddRefScriptFetchInfo(). This is decremented by
// HostReleaseScriptFetchInfo() below when the JSScript dies.
MOZ_ASSERT(GetScriptPrivate(aScript).isUndefined());
SetScriptPrivate(aScript, PrivateValue(this));
}
void ScriptFetchInfo::AssociateWithModule(JSObject* aModuleRecord) {
MOZ_ASSERT(mBaseURL);
// Make module's host defined field point to this object. The JS engine
// will increment our reference count by calling
// HostAddRefScriptFetchInfo(). This is decremented when the
// module record dies.
MOZ_ASSERT(GetModulePrivate(aModuleRecord).isUndefined());
SetModulePrivate(aModuleRecord, PrivateValue(this));
}
void HostAddRefScriptFetchInfo(
const Value& aPrivate) {
// Increment the reference count of a ScriptFetchInfo object that is
// now pointed to by a JSScript. The reference count is decremented by
// HostReleaseScriptFetchInfo() below.
auto fetchInfo = static_cast<ScriptFetchInfo*>(aPrivate.toPrivate());
fetchInfo->AddRef();
}
void HostReleaseScriptFetchInfo(
const Value& aPrivate) {
// Decrement the reference count of a ScriptFetchInfo object that was
// pointed to by a JSScript. The reference count was originally incremented by
// HostAddRefScriptFetchInfo() above.
auto fetchInfo = static_cast<ScriptFetchInfo*>(aPrivate.toPrivate());
fetchInfo->Release();
}
//////////////////////////////////////////////////////////////
// LoadedScript
//////////////////////////////////////////////////////////////
NS_IMPL_ISUPPORTS(LoadedScript, nsISupports)
LoadedScript::LoadedScript(ScriptKind aKind, nsIURI* aURI)
: mDataType(DataType::eUnknown),
mKind(aKind),
mSerializedStencilOffset(
0),
mCacheEntryId(InvalidCacheEntryId),
mIsDirty(
false),
mTookLongInPreviousRuns(
false),
mIsEverHitFromMemoryCache(
false),
mURI(aURI),
mReceivedScriptTextLength(
0) {
MOZ_ASSERT(mURI);
}
size_t LoadedScript::SizeOfIncludingThis(
mozilla::MallocSizeOf aMallocSizeOf)
const {
size_t bytes = aMallocSizeOf(this);
if (mSRIMetadata) {
bytes += mSRIMetadata->SizeOfIncludingThis(aMallocSizeOf);
}
if (IsTextSource()) {
if (IsUTF16Text()) {
bytes += ScriptText<char16_t>().sizeOfExcludingThis(aMallocSizeOf);
}
else {
bytes += ScriptText<Utf8Unit>().sizeOfExcludingThis(aMallocSizeOf);
}
}
bytes += mSRIAndSerializedStencil.sizeOfExcludingThis(aMallocSizeOf);
if (mCachedStencil) {
bytes += JS::SizeOfStencil(mCachedStencil, aMallocSizeOf);
}
return bytes;
}
nsresult LoadedScript::GetScriptSource(JSContext* aCx,
MaybeSourceText* aMaybeSource,
LoadContextBase* aMaybeLoadContext) {
// If there's no script text, we try to get it from the element
bool isWindowContext =
aMaybeLoadContext && aMaybeLoadContext->IsWindowContext();
if (isWindowContext && aMaybeLoadContext->AsWindowContext()->mIsInline) {
nsAutoString inlineData;
auto* scriptLoadContext = aMaybeLoadContext->AsWindowContext();
scriptLoadContext->GetInlineScriptText(inlineData);
size_t nbytes = inlineData.Length() *
sizeof(char16_t);
UniqueTwoByteChars chars(static_cast<char16_t*>(JS_malloc(aCx, nbytes)));
if (!chars) {
return NS_ERROR_OUT_OF_MEMORY;
}
memcpy(chars.get(), inlineData.get(), nbytes);
SourceText<char16_t> srcBuf;
if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) {
return NS_ERROR_OUT_OF_MEMORY;
}
aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf));
return NS_OK;
}
size_t length = ScriptTextLength();
if (IsUTF16Text()) {
UniqueTwoByteChars chars;
chars.reset(ScriptText<char16_t>().extractOrCopyRawBuffer());
if (!chars) {
JS_ReportOutOfMemory(aCx);
return NS_ERROR_OUT_OF_MEMORY;
}
SourceText<char16_t> srcBuf;
if (!srcBuf.init(aCx, std::move(chars), length)) {
return NS_ERROR_OUT_OF_MEMORY;
}
aMaybeSource->construct<SourceText<char16_t>>(std::move(srcBuf));
return NS_OK;
}
MOZ_ASSERT(IsUTF8Text());
mozilla::UniquePtr<Utf8Unit[], FreePolicy> chars;
chars.reset(ScriptText<Utf8Unit>().extractOrCopyRawBuffer());
if (!chars) {
JS_ReportOutOfMemory(aCx);
return NS_ERROR_OUT_OF_MEMORY;
}
SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(aCx, std::move(chars), length)) {
return NS_ERROR_OUT_OF_MEMORY;
}
aMaybeSource->construct<SourceText<Utf8Unit>>(std::move(srcBuf));
return NS_OK;
}
void LoadedScript::SetSRIMetadata(
const mozilla::dom::SRIMetadata& aSRIMetadata) {
if (aSRIMetadata.IsEmpty()) {
return;
}
mSRIMetadata = mozilla::MakeUnique<mozilla::dom::SRIMetadata>(aSRIMetadata);
}
bool LoadedScript::IsSRIMetadataReusableBy(
const mozilla::dom::SRIMetadata& aSRIMetadata) {
if (aSRIMetadata.IsEmpty()) {
return true;
}
if (!mSRIMetadata) {
return false;
}
return aSRIMetadata.CanTrustBeDelegatedTo(*mSRIMetadata);
}
//////////////////////////////////////////////////////////////
// ModuleScript
//////////////////////////////////////////////////////////////
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleScript)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ModuleScript)
tmp->mModuleRecord = nullptr;
tmp->mParseError.setUndefined();
tmp->mErrorToRethrow.setUndefined();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleScript)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleScript)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ModuleScript)
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(mParseError)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mErrorToRethrow)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
ModuleScript::ModuleScript(ScriptFetchInfo* aFetchInfo)
: mFetchInfoForAccessingPreloadFlag(aFetchInfo) {
MOZ_ASSERT(!ModuleRecord());
MOZ_ASSERT(!HasParseError());
MOZ_ASSERT(!HasErrorToRethrow());
}
void ModuleScript::Shutdown() {
if (mModuleRecord) {
ClearModuleEnvironment(mModuleRecord);
}
mModuleRecord = nullptr;
}
ModuleScript::~ModuleScript() {
// The object may be destroyed without being unlinked first.
mModuleRecord = nullptr;
mozilla::DropJSObjects(this);
}
void ModuleScript::SetModuleRecord(Handle<JSObject*> aModuleRecord) {
MOZ_ASSERT(!mModuleRecord);
MOZ_ASSERT(!HasParseError());
MOZ_ASSERT(!HasErrorToRethrow());
mModuleRecord = aModuleRecord;
#ifdef DEBUG
// Sync the [[PreloadSlot]] in ModuleObject.
if (mModuleRecord) {
SetModulePreload(mModuleRecord,
mFetchInfoForAccessingPreloadFlag->IsForModulePreload());
}
#endif
mozilla::HoldJSObjects(this);
}
void ModuleScript::SetParseError(
const Value& aError) {
MOZ_ASSERT(!aError.isUndefined());
MOZ_ASSERT(!HasParseError());
MOZ_ASSERT(!HasErrorToRethrow());
mModuleRecord = nullptr;
mParseError = aError;
mozilla::HoldJSObjects(this);
}
void ModuleScript::SetErrorToRethrow(
const Value& aError) {
MOZ_ASSERT(!aError.isUndefined());
// This is only called after SetModuleRecord() or SetParseError() so we don't
// need to call HoldJSObjects() here.
MOZ_ASSERT(ModuleRecord() || HasParseError());
mErrorToRethrow = aError;
}
void ModuleScript::SetForPreload(
bool aValue) {
mFetchInfoForAccessingPreloadFlag->SetForModulePreload(aValue);
#ifdef DEBUG
if (ModuleRecord()) {
SetModulePreload(ModuleRecord(), aValue);
}
#endif
}
void ModuleScript::SetHadImportMap(
bool aValue) { mHadImportMap = aValue; }
ResolvedModuleSet* ModuleScript::GetPreloadedResolvedSet() {
if (!mPreloadedResolvedSet) {
mPreloadedResolvedSet = mozilla::MakeUnique<ResolvedModuleSet>();
}
return mPreloadedResolvedSet.get();
}
void ModuleScript::ResetPreload() {
MOZ_ASSERT(mFetchInfoForAccessingPreloadFlag->IsForModulePreload());
if (mModuleRecord) {
ResetPreloadedModule(mModuleRecord);
}
if (HasParseError()) {
mParseError = UndefinedValue();
}
if (HasErrorToRethrow()) {
mErrorToRethrow = UndefinedValue();
}
}
}
// namespace JS::loader