/* -*- 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 "LoadedScript.h" #include "mozilla/AlreadyAddRefed.h" // already_AddRefed #include "mozilla/HoldDropJSObjects.h" #include "mozilla/RefPtr.h" // RefPtr, mozilla::MakeRefPtr #include "mozilla/UniquePtr.h" // mozilla::UniquePtr #include "mozilla/dom/ScriptLoadContext.h" // ScriptLoadContext #include "jsfriendapi.h" #include "js/Modules.h" // JS::{Get,Set}ModulePrivate #include "LoadContextBase.h" // LoadContextBase #include "nsIChannel.h" // nsIChannel namespace JS::loader { ////////////////////////////////////////////////////////////// // LoadedScript ////////////////////////////////////////////////////////////// MOZ_DEFINE_MALLOC_SIZE_OF(LoadedScriptMallocSizeOf) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LoadedScript) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END // LoadedScript can be accessed from multiple threads. // // For instance, worker script loader passes the ScriptLoadRequest and // the associated LoadedScript to the main thread to perform the actual load. // Even while it's handled by the main thread, the LoadedScript is // the target of the worker thread's cycle collector. // // Fields that can be modified by other threads shouldn't be touched by // the cycle collection. // // NOTE: nsIURI doesn't have to be touched here because it cannot be a part // of cycle. NS_IMPL_CYCLE_COLLECTION(LoadedScript, mFetchOptions, mCacheInfo) NS_IMPL_CYCLE_COLLECTING_ADDREF(LoadedScript) NS_IMPL_CYCLE_COLLECTING_RELEASE(LoadedScript) LoadedScript::LoadedScript(ScriptKind aKind, mozilla::dom::ReferrerPolicy aReferrerPolicy, ScriptFetchOptions* aFetchOptions, nsIURI* aURI) : mDataType(DataType::eUnknown), mKind(aKind), mReferrerPolicy(aReferrerPolicy), mBytecodeOffset(0), mFetchOptions(aFetchOptions), mURI(aURI), mReceivedScriptTextLength(0) { MOZ_ASSERT(mFetchOptions); MOZ_ASSERT(mURI); } LoadedScript::LoadedScript(const LoadedScript& aOther) : mDataType(DataType::eCachedStencil), mKind(aOther.mKind), mReferrerPolicy(aOther.mReferrerPolicy), mBytecodeOffset(0), mFetchOptions(aOther.mFetchOptions), mURI(aOther.mURI), mBaseURL(aOther.mBaseURL), mReceivedScriptTextLength(0), mStencil(aOther.mStencil) { MOZ_ASSERT(mFetchOptions); MOZ_ASSERT(mURI); // NOTE: This is only for the stencil case. // The script text and the bytecode are not reflected. MOZ_DIAGNOSTIC_ASSERT(aOther.mDataType == DataType::eCachedStencil); MOZ_DIAGNOSTIC_ASSERT(mStencil); MOZ_ASSERT(!mScriptData); MOZ_ASSERT(mSRIAndBytecode.empty()); } LoadedScript::~LoadedScript() { mozilla::UnregisterWeakMemoryReporter(this); mozilla::DropJSObjects(this); } void LoadedScript::RegisterMemoryReport() { mozilla::RegisterWeakMemoryReporter(this); } NS_IMETHODIMP LoadedScript::CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) { #define COLLECT_REPORT(path, kind) \ MOZ_COLLECT_REPORT(path, KIND_HEAP, UNITS_BYTES, \ SizeOfIncludingThis(LoadedScriptMallocSizeOf), \ "Memory used for LoadedScript to hold on " kind \ " across documents") switch (mKind) { case ScriptKind::eClassic: COLLECT_REPORT("explicit/js/script/loaded-script/classic", "scripts"); break; case ScriptKind::eImportMap: COLLECT_REPORT("explicit/js/script/loaded-script/import-map", "import-maps"); break; case ScriptKind::eModule: COLLECT_REPORT("explicit/js/script/loaded-script/module", "modules"); break; case ScriptKind::eEvent: COLLECT_REPORT("explicit/js/script/loaded-script/event", "event scripts"); break; } #undef COLLECT_REPORT return NS_OK; } size_t LoadedScript::SizeOfIncludingThis( mozilla::MallocSizeOf aMallocSizeOf) const { size_t bytes = aMallocSizeOf(this); if (IsTextSource()) { if (IsUTF16Text()) { bytes += ScriptText().sizeOfExcludingThis(aMallocSizeOf); } else { bytes += ScriptText().sizeOfExcludingThis(aMallocSizeOf); } } bytes += mSRIAndBytecode.sizeOfExcludingThis(aMallocSizeOf); // NOTE: Stencil is reported by SpiderMonkey. return bytes; } void LoadedScript::AssociateWithScript(JSScript* aScript) { // Verify that the rewritten URL is available when manipulating LoadedScript. MOZ_ASSERT(mBaseURL); // Set a JSScript's private value to point to this object. The JS engine will // increment our reference count by calling HostAddRefTopLevelScript(). This // is decremented by HostReleaseTopLevelScript() below when the JSScript dies. MOZ_ASSERT(GetScriptPrivate(aScript).isUndefined()); SetScriptPrivate(aScript, PrivateValue(this)); } 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(JS_malloc(aCx, nbytes))); if (!chars) { return NS_ERROR_OUT_OF_MEMORY; } memcpy(chars.get(), inlineData.get(), nbytes); SourceText srcBuf; if (!srcBuf.init(aCx, std::move(chars), inlineData.Length())) { return NS_ERROR_OUT_OF_MEMORY; } aMaybeSource->construct>(std::move(srcBuf)); return NS_OK; } size_t length = ScriptTextLength(); if (IsUTF16Text()) { UniqueTwoByteChars chars; chars.reset(ScriptText().extractOrCopyRawBuffer()); if (!chars) { JS_ReportOutOfMemory(aCx); return NS_ERROR_OUT_OF_MEMORY; } SourceText srcBuf; if (!srcBuf.init(aCx, std::move(chars), length)) { return NS_ERROR_OUT_OF_MEMORY; } aMaybeSource->construct>(std::move(srcBuf)); return NS_OK; } MOZ_ASSERT(IsUTF8Text()); mozilla::UniquePtr chars; chars.reset(ScriptText().extractOrCopyRawBuffer()); if (!chars) { JS_ReportOutOfMemory(aCx); return NS_ERROR_OUT_OF_MEMORY; } SourceText srcBuf; if (!srcBuf.init(aCx, std::move(chars), length)) { return NS_ERROR_OUT_OF_MEMORY; } aMaybeSource->construct>(std::move(srcBuf)); return NS_OK; } static bool IsInternalURIScheme(nsIURI* uri) { return uri->SchemeIs("moz-extension") || uri->SchemeIs("resource") || uri->SchemeIs("moz-src") || uri->SchemeIs("chrome"); } void LoadedScript::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)); } } inline void CheckModuleScriptPrivate(LoadedScript* script, const Value& aPrivate) { #ifdef DEBUG if (script->IsModuleScript()) { JSObject* module = script->AsModuleScript()->mModuleRecord.unbarrieredGet(); MOZ_ASSERT_IF(module, GetModulePrivate(module) == aPrivate); } #endif } void HostAddRefTopLevelScript(const Value& aPrivate) { // Increment the reference count of a LoadedScript object that is now pointed // to by a JSScript. The reference count is decremented by // HostReleaseTopLevelScript() below. auto script = static_cast(aPrivate.toPrivate()); CheckModuleScriptPrivate(script, aPrivate); script->AddRef(); } void HostReleaseTopLevelScript(const Value& aPrivate) { // Decrement the reference count of a LoadedScript object that was pointed to // by a JSScript. The reference count was originally incremented by // HostAddRefTopLevelScript() above. auto script = static_cast(aPrivate.toPrivate()); CheckModuleScriptPrivate(script, aPrivate); script->Release(); } ////////////////////////////////////////////////////////////// // EventScript ////////////////////////////////////////////////////////////// EventScript::EventScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, ScriptFetchOptions* aFetchOptions, nsIURI* aURI) : LoadedScript(ScriptKind::eEvent, aReferrerPolicy, aFetchOptions, aURI) { // EventScripts are not using ScriptLoadRequest, and mBaseURL and mURI are // the same thing. SetBaseURL(aURI); } ////////////////////////////////////////////////////////////// // ClassicScript ////////////////////////////////////////////////////////////// ClassicScript::ClassicScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, ScriptFetchOptions* aFetchOptions, nsIURI* aURI) : LoadedScript(ScriptKind::eClassic, aReferrerPolicy, aFetchOptions, aURI) { } ////////////////////////////////////////////////////////////// // ImportMapScript ////////////////////////////////////////////////////////////// ImportMapScript::ImportMapScript(mozilla::dom::ReferrerPolicy aReferrerPolicy, ScriptFetchOptions* aFetchOptions, nsIURI* aURI) : LoadedScript(ScriptKind::eImportMap, aReferrerPolicy, aFetchOptions, aURI) {} ////////////////////////////////////////////////////////////// // ModuleScript ////////////////////////////////////////////////////////////// NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(ModuleScript, LoadedScript) NS_IMPL_CYCLE_COLLECTION_CLASS(ModuleScript) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ModuleScript, LoadedScript) tmp->UnlinkModuleRecord(); tmp->mParseError.setUndefined(); tmp->mErrorToRethrow.setUndefined(); tmp->DropDiskCacheReference(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ModuleScript, LoadedScript) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ModuleScript, LoadedScript) 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(mozilla::dom::ReferrerPolicy aReferrerPolicy, ScriptFetchOptions* aFetchOptions, nsIURI* aURI) : LoadedScript(ScriptKind::eModule, aReferrerPolicy, aFetchOptions, aURI) { MOZ_ASSERT(!ModuleRecord()); MOZ_ASSERT(!HasParseError()); MOZ_ASSERT(!HasErrorToRethrow()); } ModuleScript::ModuleScript(const LoadedScript& aOther) : LoadedScript(aOther) { MOZ_ASSERT(!ModuleRecord()); MOZ_ASSERT(!HasParseError()); MOZ_ASSERT(!HasErrorToRethrow()); } /* static */ already_AddRefed ModuleScript::FromCache( const LoadedScript& aScript) { MOZ_DIAGNOSTIC_ASSERT(aScript.IsModuleScript()); MOZ_DIAGNOSTIC_ASSERT(aScript.IsCachedStencil()); return mozilla::MakeRefPtr(aScript).forget(); } already_AddRefed ModuleScript::ToCache() { MOZ_DIAGNOSTIC_ASSERT(IsCachedStencil()); MOZ_DIAGNOSTIC_ASSERT(!HasParseError()); MOZ_DIAGNOSTIC_ASSERT(!HasErrorToRethrow()); return mozilla::MakeRefPtr(*this).forget(); } void ModuleScript::Shutdown() { if (mModuleRecord) { ClearModuleEnvironment(mModuleRecord); } UnlinkModuleRecord(); } void ModuleScript::UnlinkModuleRecord() { // Remove the module record's pointer to this object if present and decrement // our reference count. The reference is added by SetModuleRecord() below. // if (mModuleRecord) { // Take care not to trigger gray unmarking because this takes a lot of time // when we're tearing down the entire page. This is safe because we are only // writing undefined into the module private, so it won't create any // black-gray edges. JSObject* module = mModuleRecord.unbarrieredGet(); if (IsCyclicModule(module)) { MOZ_ASSERT(GetModulePrivate(module).toPrivate() == this); ClearModulePrivate(module); } mModuleRecord = nullptr; } } ModuleScript::~ModuleScript() { // The object may be destroyed without being unlinked first. UnlinkModuleRecord(); } void ModuleScript::SetModuleRecord(Handle aModuleRecord) { MOZ_ASSERT(!mModuleRecord); MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasParseError()); MOZ_ASSERT_IF(IsModuleScript(), !AsModuleScript()->HasErrorToRethrow()); mModuleRecord = aModuleRecord; if (IsCyclicModule(mModuleRecord)) { // Make module's host defined field point to this object. The JS engine will // increment our reference count by calling HostAddRefTopLevelScript(). This // is decremented when the field is cleared in UnlinkModuleRecord() above or // when the module record dies. MOZ_ASSERT(GetModulePrivate(mModuleRecord).isUndefined()); SetModulePrivate(mModuleRecord, PrivateValue(this)); } mozilla::HoldJSObjects(this); } void ModuleScript::SetParseError(const Value& aError) { MOZ_ASSERT(!aError.isUndefined()); MOZ_ASSERT(!HasParseError()); MOZ_ASSERT(!HasErrorToRethrow()); UnlinkModuleRecord(); 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) { mForPreload = aValue; } void ModuleScript::SetHadImportMap(bool aValue) { mHadImportMap = aValue; } } // namespace JS::loader