This reverts commit1abc6298e1. Revert "Bug 2056134 - Part 5: Add tests. r=bthrall" This reverts commit7bb9240798. Revert "Bug 2056134 - Part 4: Use pre-calculated fallback encoding for the classic script disk cache MIME-Type. r=bthrall" This reverts commit07b098aa0d. Revert "Bug 2056134 - Part 3: Use pre-calculated fallback encoding for the in-memory cache key. r=bthrall" This reverts commit3173e731bf. Revert "Bug 2056134 - Part 2: Pre-calculate the fallback encoding for the classic script source. r=bthrall,dom-worker-reviewers,edenchuang" This reverts commitfd0eb69272. Revert "Bug 2056134 - Part 1: Include the hint charset into the cache key string for the ChromeUtils.getCachedJavaScriptSource. r=bthrall,devtools-reviewers,jdescottes" This reverts commitc0bc4e330d.
756 lines
21 KiB
C++
756 lines
21 KiB
C++
/* 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 "SharedScriptCache.h"
|
|
|
|
#include "ScriptLoadHandler.h" // ScriptLoadHandler
|
|
#include "ScriptLoader.h" // ScriptLoader
|
|
#include "ScriptTrace.h" // TRACE_FOR_TEST
|
|
#include "js/RootingAPI.h" // JS::MutableHandle
|
|
#include "js/Value.h" // JS::Value
|
|
#include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext
|
|
#include "js/experimental/JSStencil.h" // JS::GetScriptSourceText
|
|
#include "mozilla/HalTypes.h" // hal::CONTENT_PROCESS_ID_*
|
|
#include "mozilla/Maybe.h" // Maybe, Some, Nothing
|
|
#include "mozilla/TaskController.h" // TaskController, Task
|
|
#include "mozilla/dom/ContentChild.h" // dom::ContentChild
|
|
#include "mozilla/dom/ContentParent.h" // dom::ContentParent
|
|
#include "mozilla/glean/DomMetrics.h" // mozilla::glean::dom::*
|
|
#include "nsIMemoryReporter.h" // nsIMemoryReporter, MOZ_DEFINE_MALLOC_SIZE_OF, RegisterWeakMemoryReporter, UnregisterWeakMemoryReporter, MOZ_COLLECT_REPORT, KIND_HEAP, UNITS_BYTES
|
|
#include "nsIPrefBranch.h" // nsIPrefBranch, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
|
|
#include "nsIPrefService.h" // NS_PREFSERVICE_CONTRACTID
|
|
#include "nsIPrincipal.h" // nsIPrincipal
|
|
#include "nsIPropertyBag2.h" // nsIPropertyBag2
|
|
#include "nsISupportsImpl.h" // NS_IMPL_ISUPPORTS
|
|
#include "nsStringFwd.h" // nsACString
|
|
|
|
namespace mozilla::dom {
|
|
|
|
ScriptHashKey::ScriptHashKey(
|
|
ScriptLoader* aLoader, const JS::loader::ScriptLoadRequest* aRequest,
|
|
mozilla::dom::ReferrerPolicy aReferrerPolicy,
|
|
const JS::loader::ScriptFetchOptions* aFetchOptions,
|
|
const nsCOMPtr<nsIURI> aURI)
|
|
: PLDHashEntryHdr(),
|
|
mURI(aURI),
|
|
mPartitionPrincipal(aLoader->PartitionedPrincipal()),
|
|
mLoaderPrincipal(aLoader->LoaderPrincipal()),
|
|
mKind(aRequest->mKind),
|
|
mCORSMode(aFetchOptions->mCORSMode),
|
|
mReferrerPolicy(aReferrerPolicy) {
|
|
if (mKind == JS::loader::ScriptKind::eClassic) {
|
|
if (aRequest->GetScriptLoadContext()->HasScriptElement()) {
|
|
aRequest->GetScriptLoadContext()->GetHintCharset(mHintCharset);
|
|
}
|
|
}
|
|
|
|
MOZ_COUNT_CTOR(ScriptHashKey);
|
|
}
|
|
|
|
ScriptHashKey::ScriptHashKey(const ScriptLoadData& aLoadData)
|
|
: ScriptHashKey(aLoadData.CacheKey()) {}
|
|
|
|
bool ScriptHashKey::KeyEquals(const ScriptHashKey& aKey) const {
|
|
{
|
|
bool eq;
|
|
if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
// NOTE: mLoaderPrincipal is only for the SharedSubResourceCache logic,
|
|
// not for comparison here.
|
|
|
|
if (mKind != aKey.mKind) {
|
|
return false;
|
|
}
|
|
|
|
if (mCORSMode != aKey.mCORSMode) {
|
|
return false;
|
|
}
|
|
|
|
if (mReferrerPolicy != aKey.mReferrerPolicy) {
|
|
return false;
|
|
}
|
|
|
|
// NOTE: module always use UTF-8.
|
|
if (mKind == JS::loader::ScriptKind::eClassic) {
|
|
if (mHintCharset != aKey.mHintCharset) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptHashKey::ToStringForLookup(nsACString& aResult) {
|
|
aResult.Truncate();
|
|
|
|
aResult.AppendLiteral("SharedScriptCache:");
|
|
switch (mKind) {
|
|
case JS::loader::ScriptKind::eClassic:
|
|
aResult.Append('c');
|
|
break;
|
|
case JS::loader::ScriptKind::eModule:
|
|
aResult.Append('m');
|
|
break;
|
|
case JS::loader::ScriptKind::eEvent:
|
|
aResult.Append('e');
|
|
break;
|
|
case JS::loader::ScriptKind::eImportMap:
|
|
aResult.Append('i');
|
|
break;
|
|
case JS::loader::ScriptKind::eSpeculationRules:
|
|
aResult.Append('s');
|
|
break;
|
|
}
|
|
|
|
switch (mCORSMode) {
|
|
case CORS_NONE:
|
|
aResult.Append('n');
|
|
break;
|
|
case CORS_ANONYMOUS:
|
|
aResult.Append('a');
|
|
break;
|
|
case CORS_USE_CREDENTIALS:
|
|
aResult.Append('c');
|
|
break;
|
|
}
|
|
|
|
switch (mReferrerPolicy) {
|
|
case ReferrerPolicy::_empty:
|
|
aResult.Append('_');
|
|
break;
|
|
case ReferrerPolicy::No_referrer:
|
|
aResult.Append('n');
|
|
break;
|
|
case ReferrerPolicy::No_referrer_when_downgrade:
|
|
aResult.Append('d');
|
|
break;
|
|
case ReferrerPolicy::Origin:
|
|
aResult.Append('o');
|
|
break;
|
|
case ReferrerPolicy::Origin_when_cross_origin:
|
|
aResult.Append('c');
|
|
break;
|
|
case ReferrerPolicy::Unsafe_url:
|
|
aResult.Append('u');
|
|
break;
|
|
case ReferrerPolicy::Same_origin:
|
|
aResult.Append('s');
|
|
break;
|
|
case ReferrerPolicy::Strict_origin:
|
|
aResult.Append('S');
|
|
break;
|
|
case ReferrerPolicy::Strict_origin_when_cross_origin:
|
|
aResult.Append('C');
|
|
break;
|
|
}
|
|
|
|
nsAutoCString partitionPrincipal;
|
|
BasePrincipal::Cast(mPartitionPrincipal)->ToJSON(partitionPrincipal);
|
|
aResult.Append(partitionPrincipal);
|
|
}
|
|
|
|
/* static */
|
|
Maybe<ScriptHashKey> ScriptHashKey::FromStringsForLookup(
|
|
const nsACString& aKey, const nsACString& aURI,
|
|
const nsACString& aHintCharset) {
|
|
if (aKey.Length() < 22) {
|
|
return Nothing();
|
|
}
|
|
|
|
if (Substring(aKey, 0, 18) != "SharedScriptCache:") {
|
|
return Nothing();
|
|
}
|
|
|
|
JS::loader::ScriptKind kind;
|
|
char kindChar = aKey[18];
|
|
if (kindChar == 'c') {
|
|
kind = JS::loader::ScriptKind::eClassic;
|
|
} else if (kindChar == 'm') {
|
|
kind = JS::loader::ScriptKind::eModule;
|
|
} else if (kindChar == 'e') {
|
|
kind = JS::loader::ScriptKind::eEvent;
|
|
} else if (kindChar == 'i') {
|
|
kind = JS::loader::ScriptKind::eImportMap;
|
|
} else if (kindChar == 's') {
|
|
kind = JS::loader::ScriptKind::eSpeculationRules;
|
|
} else {
|
|
return Nothing();
|
|
}
|
|
|
|
CORSMode corsMode;
|
|
char corsModeChar = aKey[19];
|
|
if (corsModeChar == 'n') {
|
|
corsMode = CORS_NONE;
|
|
} else if (corsModeChar == 'a') {
|
|
corsMode = CORS_ANONYMOUS;
|
|
} else if (corsModeChar == 'c') {
|
|
corsMode = CORS_USE_CREDENTIALS;
|
|
} else {
|
|
return Nothing();
|
|
}
|
|
|
|
mozilla::dom::ReferrerPolicy referrerPolicy;
|
|
char referrerPolicyChar = aKey[20];
|
|
if (referrerPolicyChar == '_') {
|
|
referrerPolicy = ReferrerPolicy::_empty;
|
|
} else if (referrerPolicyChar == 'n') {
|
|
referrerPolicy = ReferrerPolicy::No_referrer;
|
|
} else if (referrerPolicyChar == 'd') {
|
|
referrerPolicy = ReferrerPolicy::No_referrer_when_downgrade;
|
|
} else if (referrerPolicyChar == 'o') {
|
|
referrerPolicy = ReferrerPolicy::Origin;
|
|
} else if (referrerPolicyChar == 'c') {
|
|
referrerPolicy = ReferrerPolicy::Origin_when_cross_origin;
|
|
} else if (referrerPolicyChar == 'u') {
|
|
referrerPolicy = ReferrerPolicy::Unsafe_url;
|
|
} else if (referrerPolicyChar == 's') {
|
|
referrerPolicy = ReferrerPolicy::Same_origin;
|
|
} else if (referrerPolicyChar == 'S') {
|
|
referrerPolicy = ReferrerPolicy::Strict_origin;
|
|
} else if (referrerPolicyChar == 'C') {
|
|
referrerPolicy = ReferrerPolicy::Strict_origin_when_cross_origin;
|
|
} else {
|
|
return Nothing();
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> partitionPrincipal =
|
|
BasePrincipal::FromJSON(Substring(aKey, 21));
|
|
if (!partitionPrincipal) {
|
|
return Nothing();
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI);
|
|
if (NS_FAILED(rv)) {
|
|
return Nothing();
|
|
}
|
|
|
|
return Some(ScriptHashKey(uri, partitionPrincipal, kind, corsMode,
|
|
referrerPolicy,
|
|
NS_ConvertUTF8toUTF16(aHintCharset)));
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ScriptLoadData, nsISupports)
|
|
|
|
ScriptLoadData::ScriptLoadData(ScriptLoader* aLoader,
|
|
JS::loader::ScriptLoadRequest* aRequest,
|
|
CacheExpirationTime aExpirationTime,
|
|
JS::loader::LoadedScript* aLoadedScript)
|
|
: mExpirationTime(aExpirationTime),
|
|
mLoader(aLoader),
|
|
mKey(aLoader, aRequest, aRequest->ReferrerPolicy(),
|
|
aRequest->FetchOptions(), aLoadedScript->GetURI()),
|
|
mLoadedScript(aLoadedScript),
|
|
mNetworkMetadata(aRequest->mNetworkMetadata) {}
|
|
|
|
NS_IMPL_ISUPPORTS(SharedScriptCache, nsIMemoryReporter, nsIObserver)
|
|
|
|
MOZ_DEFINE_MALLOC_SIZE_OF(SharedScriptCacheMallocSizeOf)
|
|
|
|
SharedScriptCache::SharedScriptCache() = default;
|
|
|
|
void SharedScriptCache::Init() {
|
|
RegisterWeakMemoryReporter(this);
|
|
|
|
// URL classification (tracking protection etc) are handled inside
|
|
// nsHttpChannel.
|
|
// The cache reflects the policy for whether to block or not, and once
|
|
// the policy is modified, we should discard the cache, to avoid running
|
|
// a cached script which is supposed to be blocked.
|
|
auto ClearCache = [](const char*, void*) { Clear(); };
|
|
Preferences::RegisterPrefixCallback(ClearCache, "urlclassifier.");
|
|
Preferences::RegisterCallback(ClearCache,
|
|
"privacy.trackingprotection.enabled");
|
|
}
|
|
|
|
SharedScriptCache::~SharedScriptCache() {
|
|
UnregisterWeakMemoryReporter(this);
|
|
ClearDiskCacheTimer();
|
|
}
|
|
|
|
bool SharedScriptCache::ShouldIgnoreMemoryPressure() {
|
|
// During the automated testing, we need to ignore the memory pressure,
|
|
// in order to get the deterministic result.
|
|
return !StaticPrefs::
|
|
dom_script_loader_experimental_navigation_cache_check_memory_pressure();
|
|
}
|
|
|
|
void SharedScriptCache::ClearInProcessForMemoryPressure() {
|
|
for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
|
|
iter.Data().mResource->InvalidateCachedStencil();
|
|
}
|
|
|
|
SharedSubResourceCache::ClearInProcessForMemoryPressure();
|
|
}
|
|
|
|
void SharedScriptCache::LoadCompleted(SharedScriptCache* aCache,
|
|
ScriptLoadData& aData) {}
|
|
|
|
NS_IMETHODIMP
|
|
SharedScriptCache::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|
nsISupports* aData, bool aAnonymize) {
|
|
MOZ_COLLECT_REPORT("explicit/js-non-window/cache", KIND_HEAP, UNITS_BYTES,
|
|
SharedScriptCacheMallocSizeOf(this) +
|
|
SizeOfExcludingThis(SharedScriptCacheMallocSizeOf),
|
|
"Memory used for SharedScriptCache to share script "
|
|
"across documents");
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
void SharedScriptCache::Clear(const Maybe<bool>& aChrome,
|
|
const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal,
|
|
const Maybe<nsCString>& aSchemelessSite,
|
|
const Maybe<OriginAttributesPattern>& aPattern,
|
|
const Maybe<nsCString>& aURL) {
|
|
using ContentParent = dom::ContentParent;
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
|
|
(void)cp->SendClearScriptCache(aChrome, aPrincipal, aSchemelessSite,
|
|
aPattern, aURL);
|
|
}
|
|
}
|
|
|
|
if (sSingleton) {
|
|
sSingleton->ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern,
|
|
aURL);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void SharedScriptCache::Invalidate() {
|
|
using ContentParent = dom::ContentParent;
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
|
|
(void)cp->SendInvalidateScriptCache();
|
|
}
|
|
}
|
|
|
|
if (sSingleton) {
|
|
sSingleton->InvalidateInProcess();
|
|
}
|
|
TRACE_FOR_TEST_0("memorycache:invalidate");
|
|
}
|
|
|
|
/* static */
|
|
bool SharedScriptCache::GetCachedScriptSource(
|
|
JSContext* aCx, const nsACString& aKey, const nsACString& aURI,
|
|
const nsACString& aHintCharset, JS::MutableHandle<JS::Value> aRetval) {
|
|
if (!sSingleton) {
|
|
aRetval.setUndefined();
|
|
return true;
|
|
}
|
|
|
|
Maybe<ScriptHashKey> maybeKey =
|
|
ScriptHashKey::FromStringsForLookup(aKey, aURI, aHintCharset);
|
|
if (!maybeKey) {
|
|
aRetval.setUndefined();
|
|
return true;
|
|
}
|
|
|
|
JS::Stencil* stencil = nullptr;
|
|
if (auto lookup = sSingleton->mComplete.Lookup(*maybeKey)) {
|
|
JS::loader::LoadedScript* loadedScript = lookup.Data().mResource;
|
|
if (loadedScript->IsInvalidatedCachedStencil()) {
|
|
aRetval.setUndefined();
|
|
return true;
|
|
}
|
|
|
|
// NOTE: We don't check the SRIMetadata here, because this is not a
|
|
// request from <script> element.
|
|
stencil = loadedScript->GetCachedStencil();
|
|
} else {
|
|
aRetval.setUndefined();
|
|
return true;
|
|
}
|
|
|
|
if (!JS::GetScriptSourceText(aCx, stencil, aRetval)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SharedScriptCache::InvalidateInProcess() {
|
|
for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
|
|
if (!iter.Data().mResource->HasCacheEntryId()) {
|
|
iter.Remove();
|
|
} else {
|
|
iter.Data().mResource->SetDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void SharedScriptCache::PrepareForLastCC() {
|
|
if (sSingleton) {
|
|
sSingleton->mComplete.Clear();
|
|
sSingleton->mPending.Clear();
|
|
sSingleton->mLoading.Clear();
|
|
}
|
|
}
|
|
|
|
static bool ShouldSave(JS::loader::LoadedScript* aLoadedScript,
|
|
JS::Stencil* aStencil,
|
|
ScriptLoader::DiskCacheStrategy aStrategy) {
|
|
if (!aLoadedScript->HasDiskCacheReference()) {
|
|
return false;
|
|
}
|
|
|
|
if (!aLoadedScript->HasSRI()) {
|
|
return false;
|
|
}
|
|
|
|
if (aStrategy.mHasSourceLengthMin) {
|
|
size_t len = JS::GetScriptSourceLength(aStencil);
|
|
if (len < aStrategy.mSourceLengthMin) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (aStrategy.mHasFetchCountMin) {
|
|
if (aLoadedScript->mFetchCount < aStrategy.mFetchCountMin) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SharedScriptCache::MaybeScheduleUpdateDiskCache() {
|
|
auto strategy = ScriptLoader::GetDiskCacheStrategy();
|
|
if (strategy.mIsDisabled) {
|
|
return false;
|
|
}
|
|
|
|
bool hasSaveable = false;
|
|
for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
|
|
JS::loader::LoadedScript* loadedScript = iter.Data().mResource;
|
|
if (loadedScript->IsInvalidatedCachedStencil()) {
|
|
continue;
|
|
}
|
|
|
|
JS::Stencil* stencil = loadedScript->GetCachedStencil();
|
|
if (ShouldSave(loadedScript, stencil, strategy)) {
|
|
hasSaveable = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hasSaveable) {
|
|
return false;
|
|
}
|
|
|
|
SetDiskCacheTimer();
|
|
return true;
|
|
}
|
|
|
|
void SharedScriptCache::SetDiskCacheTimer() {
|
|
if (mDiskCacheTimer) {
|
|
mRetryDiskCacheTimer = true;
|
|
return;
|
|
}
|
|
|
|
auto result = NS_NewTimerWithFuncCallback(
|
|
[](nsITimer*, void* aClosure) {
|
|
auto* self = static_cast<SharedScriptCache*>(aClosure);
|
|
self->OnDiskCacheTimer();
|
|
},
|
|
this, StaticPrefs::dom_script_loader_disk_cache_delay_ms(),
|
|
nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
|
|
"SharedScriptCache::DiskCacheTimer"_ns);
|
|
|
|
if (result.isErr()) {
|
|
return;
|
|
}
|
|
|
|
mDiskCacheTimer = result.unwrap();
|
|
}
|
|
|
|
void SharedScriptCache::ClearDiskCacheTimer() {
|
|
if (!mDiskCacheTimer) {
|
|
return;
|
|
}
|
|
|
|
mDiskCacheTimer->Cancel();
|
|
mDiskCacheTimer = nullptr;
|
|
}
|
|
|
|
void SharedScriptCache::OnDiskCacheTimer() {
|
|
mDiskCacheTimer = nullptr;
|
|
if (mRetryDiskCacheTimer) {
|
|
mRetryDiskCacheTimer = false;
|
|
SetDiskCacheTimer();
|
|
return;
|
|
}
|
|
|
|
UpdateDiskCache();
|
|
}
|
|
|
|
class ScriptEncodeAndCompressionTask : public mozilla::Task {
|
|
public:
|
|
ScriptEncodeAndCompressionTask()
|
|
: Task(Kind::OffMainThreadOnly, EventQueuePriority::Idle) {}
|
|
virtual ~ScriptEncodeAndCompressionTask() = default;
|
|
|
|
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
|
|
bool GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("ScriptEncodeAndCompressionTask");
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
TaskResult Run() override {
|
|
SharedScriptCache::Get()->EncodeAndCompress();
|
|
return TaskResult::Complete;
|
|
}
|
|
};
|
|
|
|
class ScriptSaveTask : public mozilla::Task {
|
|
public:
|
|
ScriptSaveTask() : Task(Kind::MainThreadOnly, EventQueuePriority::Idle) {}
|
|
virtual ~ScriptSaveTask() = default;
|
|
|
|
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
|
|
bool GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("ScriptSaveTask");
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
TaskResult Run() override {
|
|
SharedScriptCache::Get()->SaveToDiskCache();
|
|
return TaskResult::Complete;
|
|
}
|
|
};
|
|
|
|
void SharedScriptCache::UpdateDiskCache() {
|
|
auto strategy = ScriptLoader::GetDiskCacheStrategy();
|
|
if (strategy.mIsDisabled) {
|
|
return;
|
|
}
|
|
|
|
mozilla::MutexAutoLock lock(mEncodeMutex);
|
|
|
|
if (!mEncodeItems.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) {
|
|
JS::loader::LoadedScript* loadedScript = iter.Data().mResource;
|
|
if (loadedScript->IsInvalidatedCachedStencil()) {
|
|
continue;
|
|
}
|
|
|
|
RefPtr<JS::Stencil> stencil = loadedScript->GetCachedStencil();
|
|
if (!ShouldSave(loadedScript, stencil, strategy)) {
|
|
continue;
|
|
}
|
|
|
|
if (!mEncodeItems.emplaceBack(stencil, std::move(loadedScript->SRI()),
|
|
loadedScript)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (mEncodeItems.empty()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<ScriptEncodeAndCompressionTask> encodeTask =
|
|
new ScriptEncodeAndCompressionTask();
|
|
RefPtr<ScriptSaveTask> saveTask = new ScriptSaveTask();
|
|
saveTask->AddDependency(encodeTask);
|
|
|
|
TaskController::Get()->AddTask(encodeTask.forget());
|
|
TaskController::Get()->AddTask(saveTask.forget());
|
|
}
|
|
|
|
void SharedScriptCache::EncodeAndCompress() {
|
|
JS::FrontendContext* fc = JS::NewFrontendContext();
|
|
if (!fc) {
|
|
return;
|
|
}
|
|
|
|
mozilla::MutexAutoLock lock(mEncodeMutex);
|
|
|
|
for (auto& item : mEncodeItems) {
|
|
if (!ScriptLoader::EncodeAndCompress(fc, item.mLoadedScript, item.mStencil,
|
|
item.mSRI, item.mCompressed)) {
|
|
item.mCompressed.clear();
|
|
}
|
|
}
|
|
|
|
JS::DestroyFrontendContext(fc);
|
|
}
|
|
|
|
void SharedScriptCache::SaveToDiskCache() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mozilla::MutexAutoLock lock(mEncodeMutex);
|
|
|
|
for (const auto& item : mEncodeItems) {
|
|
if (item.mCompressed.empty()) {
|
|
item.mLoadedScript->DropDiskCacheReference();
|
|
item.mLoadedScript->DropSRIOrSRIAndSerializedStencil();
|
|
TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed");
|
|
continue;
|
|
}
|
|
|
|
if (!ScriptLoader::SaveToDiskCache(item.mLoadedScript, item.mCompressed)) {
|
|
item.mLoadedScript->DropDiskCacheReference();
|
|
item.mLoadedScript->DropSRIOrSRIAndSerializedStencil();
|
|
TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed");
|
|
}
|
|
|
|
item.mLoadedScript->DropDiskCacheReference();
|
|
item.mLoadedScript->DropSRIOrSRIAndSerializedStencil();
|
|
TRACE_FOR_TEST(item.mLoadedScript, "diskcache:saved");
|
|
}
|
|
|
|
mEncodeItems.clear();
|
|
}
|
|
|
|
void SharedScriptCache::OnEntryInserted() { mEntryInserted++; }
|
|
|
|
void SharedScriptCache::OnEntryEverHit() { mEntryEverHit++; }
|
|
|
|
void SharedScriptCache::UpdateEverHitTelemetry() {
|
|
if (mEntryInserted == 0) {
|
|
return;
|
|
}
|
|
|
|
uint32_t rate = mEntryEverHit * 100 / mEntryInserted;
|
|
if (rate == mLastEverHitRatio) {
|
|
return;
|
|
}
|
|
mLastEverHitRatio = rate;
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
if (!EnsureEverHitMap()) {
|
|
return;
|
|
}
|
|
(void)mEverHitMap->put(hal::CONTENT_PROCESS_ID_MAIN, rate);
|
|
return;
|
|
}
|
|
|
|
if (!XRE_IsContentProcess()) {
|
|
return;
|
|
}
|
|
auto* cc = ContentChild::GetSingleton();
|
|
if (!cc) {
|
|
return;
|
|
}
|
|
|
|
uint64_t childId = cc->GetID();
|
|
cc->SendUpdateScriptCacheEverHitTelemetry(childId, rate);
|
|
}
|
|
|
|
/* static */
|
|
void SharedScriptCache::RecvUpdateEverHitTelemetry(const uint64_t& aChildId,
|
|
const uint32_t& aRate) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
SharedScriptCache* self = SharedScriptCache::Get();
|
|
if (!self) {
|
|
return;
|
|
}
|
|
if (!self->EnsureEverHitMap()) {
|
|
return;
|
|
}
|
|
(void)self->mEverHitMap->put(aChildId, aRate);
|
|
}
|
|
|
|
bool SharedScriptCache::EnsureEverHitMap() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (mPreparedEverHitMap) {
|
|
return !!mEverHitMap;
|
|
}
|
|
mPreparedEverHitMap = true;
|
|
|
|
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
|
|
if (!os) {
|
|
return false;
|
|
}
|
|
os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak= */ false);
|
|
os->AddObserver(this, "profile-before-change", /* ownsWeak= */ false);
|
|
|
|
mEverHitMap.reset(new EverHitMapType());
|
|
return !!mEverHitMap;
|
|
}
|
|
|
|
// When a content process gets shutdown, accumulate the latest cache-hit ratio
|
|
// to the telemetry, and remove the entry from the map, to avoid keeping the
|
|
// data unnecessarily longer.
|
|
void SharedScriptCache::OnContentShutdown(nsISupports* aSubject) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (!mEverHitMap) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
|
|
if (!props) {
|
|
return;
|
|
}
|
|
|
|
uint64_t childID = hal::CONTENT_PROCESS_ID_UNKNOWN;
|
|
props->GetPropertyAsUint64(u"childID"_ns, &childID);
|
|
if (childID == hal::CONTENT_PROCESS_ID_UNKNOWN) {
|
|
return;
|
|
}
|
|
|
|
auto p = mEverHitMap->lookup(childID);
|
|
if (!p) {
|
|
return;
|
|
}
|
|
AccumulateEverHitTelemetry(p->value());
|
|
mEverHitMap->remove(p);
|
|
}
|
|
|
|
// When the parent process is getting shutdown, accumulate the latest cache-hit
|
|
// ratio of all processes to the telemetry.
|
|
//
|
|
// We perform this at ShutdownPhase::AppShutdown phase.
|
|
// Glean submits the telemetry at ShutdownPhase::AppShutdownTelemetry, which
|
|
// is after the ShutdownPhase::AppShutdown phase.
|
|
void SharedScriptCache::OnProfileBeforeChange() {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (!mEverHitMap) {
|
|
return;
|
|
}
|
|
|
|
for (auto iter = mEverHitMap->iter(); !iter.done(); iter.next()) {
|
|
AccumulateEverHitTelemetry(iter.get().value());
|
|
}
|
|
mEverHitMap->clear();
|
|
mEverHitMap.reset();
|
|
}
|
|
|
|
void SharedScriptCache::AccumulateEverHitTelemetry(uint32_t aRate) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
using namespace mozilla::glean::dom;
|
|
|
|
// Skip this function if we are not running telemetry.
|
|
if (!mozilla::Telemetry::CanRecordExtended()) {
|
|
return;
|
|
}
|
|
|
|
script_memory_cache_ever_hit.AccumulateSingleSample(aRate);
|
|
}
|
|
|
|
} // namespace mozilla::dom
|