SharedScriptCache.cpp (11844B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SharedScriptCache.h" 8 9 #include "ScriptLoadHandler.h" // ScriptLoadHandler 10 #include "ScriptLoader.h" // ScriptLoader 11 #include "ScriptTrace.h" // TRACE_FOR_TEST 12 #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext 13 #include "mozilla/Maybe.h" // Maybe, Some, Nothing 14 #include "mozilla/TaskController.h" // TaskController, Task 15 #include "mozilla/dom/ContentParent.h" // dom::ContentParent 16 #include "nsIMemoryReporter.h" // nsIMemoryReporter, MOZ_DEFINE_MALLOC_SIZE_OF, RegisterWeakMemoryReporter, UnregisterWeakMemoryReporter, MOZ_COLLECT_REPORT, KIND_HEAP, UNITS_BYTES 17 #include "nsIPrefBranch.h" // nsIPrefBranch, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID 18 #include "nsIPrefService.h" // NS_PREFSERVICE_CONTRACTID 19 #include "nsIPrincipal.h" // nsIPrincipal 20 #include "nsISupportsImpl.h" // NS_IMPL_ISUPPORTS 21 #include "nsStringFwd.h" // nsACString 22 23 namespace mozilla::dom { 24 25 ScriptHashKey::ScriptHashKey( 26 ScriptLoader* aLoader, const JS::loader::ScriptLoadRequest* aRequest, 27 mozilla::dom::ReferrerPolicy aReferrerPolicy, 28 const JS::loader::ScriptFetchOptions* aFetchOptions, 29 const nsCOMPtr<nsIURI> aURI) 30 : PLDHashEntryHdr(), 31 mURI(aURI), 32 mPartitionPrincipal(aLoader->PartitionedPrincipal()), 33 mLoaderPrincipal(aLoader->LoaderPrincipal()), 34 mKind(aRequest->mKind), 35 mCORSMode(aFetchOptions->mCORSMode), 36 mReferrerPolicy(aReferrerPolicy), 37 mSRIMetadata(aRequest->mIntegrity), 38 mNonce(aFetchOptions->mNonce) { 39 if (mKind == JS::loader::ScriptKind::eClassic) { 40 if (aRequest->GetScriptLoadContext()->HasScriptElement()) { 41 aRequest->GetScriptLoadContext()->GetHintCharset(mHintCharset); 42 } 43 } 44 45 MOZ_COUNT_CTOR(ScriptHashKey); 46 } 47 48 ScriptHashKey::ScriptHashKey(ScriptLoader* aLoader, 49 const JS::loader::ScriptLoadRequest* aRequest, 50 const JS::loader::LoadedScript* aLoadedScript) 51 : ScriptHashKey(aLoader, aRequest, aLoadedScript->ReferrerPolicy(), 52 aLoadedScript->GetFetchOptions(), aLoadedScript->GetURI()) { 53 } 54 55 ScriptHashKey::ScriptHashKey(const ScriptLoadData& aLoadData) 56 : ScriptHashKey(aLoadData.CacheKey()) {} 57 58 bool ScriptHashKey::KeyEquals(const ScriptHashKey& aKey) const { 59 { 60 bool eq; 61 if (NS_FAILED(mURI->Equals(aKey.mURI, &eq)) || !eq) { 62 return false; 63 } 64 } 65 66 if (!mPartitionPrincipal->Equals(aKey.mPartitionPrincipal)) { 67 return false; 68 } 69 70 // NOTE: mLoaderPrincipal is only for the SharedSubResourceCache logic, 71 // not for comparison here. 72 73 if (mKind != aKey.mKind) { 74 return false; 75 } 76 77 if (mCORSMode != aKey.mCORSMode) { 78 return false; 79 } 80 81 if (mReferrerPolicy != aKey.mReferrerPolicy) { 82 return false; 83 } 84 85 if (!mSRIMetadata.CanTrustBeDelegatedTo(aKey.mSRIMetadata) || 86 !aKey.mSRIMetadata.CanTrustBeDelegatedTo(mSRIMetadata)) { 87 return false; 88 } 89 90 if (mNonce != aKey.mNonce) { 91 return false; 92 } 93 94 // NOTE: module always use UTF-8. 95 if (mKind == JS::loader::ScriptKind::eClassic) { 96 if (mHintCharset != aKey.mHintCharset) { 97 return false; 98 } 99 } 100 101 return true; 102 } 103 104 NS_IMPL_ISUPPORTS(ScriptLoadData, nsISupports) 105 106 ScriptLoadData::ScriptLoadData(ScriptLoader* aLoader, 107 JS::loader::ScriptLoadRequest* aRequest, 108 JS::loader::LoadedScript* aLoadedScript) 109 : mExpirationTime(aRequest->ExpirationTime()), 110 mLoader(aLoader), 111 mKey(aLoader, aRequest, aLoadedScript), 112 mLoadedScript(aLoadedScript), 113 mNetworkMetadata(aRequest->mNetworkMetadata) {} 114 115 NS_IMPL_ISUPPORTS(SharedScriptCache, nsIMemoryReporter) 116 117 MOZ_DEFINE_MALLOC_SIZE_OF(SharedScriptCacheMallocSizeOf) 118 119 SharedScriptCache::SharedScriptCache() = default; 120 121 void SharedScriptCache::Init() { 122 RegisterWeakMemoryReporter(this); 123 124 // URL classification (tracking protection etc) are handled inside 125 // nsHttpChannel. 126 // The cache reflects the policy for whether to block or not, and once 127 // the policy is modified, we should discard the cache, to avoid running 128 // a cached script which is supposed to be blocked. 129 auto ClearCache = [](const char*, void*) { Clear(); }; 130 Preferences::RegisterPrefixCallback(ClearCache, "urlclassifier."); 131 Preferences::RegisterCallback(ClearCache, 132 "privacy.trackingprotection.enabled"); 133 } 134 135 SharedScriptCache::~SharedScriptCache() { UnregisterWeakMemoryReporter(this); } 136 137 void SharedScriptCache::LoadCompleted(SharedScriptCache* aCache, 138 ScriptLoadData& aData) {} 139 140 NS_IMETHODIMP 141 SharedScriptCache::CollectReports(nsIHandleReportCallback* aHandleReport, 142 nsISupports* aData, bool aAnonymize) { 143 MOZ_COLLECT_REPORT("explicit/js-non-window/cache", KIND_HEAP, UNITS_BYTES, 144 SharedScriptCacheMallocSizeOf(this) + 145 SizeOfExcludingThis(SharedScriptCacheMallocSizeOf), 146 "Memory used for SharedScriptCache to share script " 147 "across documents"); 148 return NS_OK; 149 } 150 151 /* static */ 152 void SharedScriptCache::Clear(const Maybe<bool>& aChrome, 153 const Maybe<nsCOMPtr<nsIPrincipal>>& aPrincipal, 154 const Maybe<nsCString>& aSchemelessSite, 155 const Maybe<OriginAttributesPattern>& aPattern, 156 const Maybe<nsCString>& aURL) { 157 using ContentParent = dom::ContentParent; 158 159 if (XRE_IsParentProcess()) { 160 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { 161 (void)cp->SendClearScriptCache(aChrome, aPrincipal, aSchemelessSite, 162 aPattern, aURL); 163 } 164 } 165 166 if (sSingleton) { 167 sSingleton->ClearInProcess(aChrome, aPrincipal, aSchemelessSite, aPattern, 168 aURL); 169 } 170 } 171 172 /* static */ 173 void SharedScriptCache::Invalidate() { 174 using ContentParent = dom::ContentParent; 175 176 if (XRE_IsParentProcess()) { 177 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { 178 (void)cp->SendInvalidateScriptCache(); 179 } 180 } 181 182 if (sSingleton) { 183 sSingleton->InvalidateInProcess(); 184 } 185 TRACE_FOR_TEST_0("memorycache:invalidate"); 186 } 187 188 void SharedScriptCache::InvalidateInProcess() { 189 for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) { 190 if (!iter.Data().mResource->HasCacheEntryId()) { 191 iter.Remove(); 192 } else { 193 iter.Data().mResource->SetDirty(); 194 } 195 } 196 } 197 198 /* static */ 199 void SharedScriptCache::PrepareForLastCC() { 200 if (sSingleton) { 201 sSingleton->mComplete.Clear(); 202 sSingleton->mPending.Clear(); 203 sSingleton->mLoading.Clear(); 204 } 205 } 206 207 static bool ShouldSave(JS::loader::LoadedScript* aLoadedScript, 208 ScriptLoader::DiskCacheStrategy aStrategy) { 209 if (!aLoadedScript->HasDiskCacheReference()) { 210 return false; 211 } 212 213 if (!aLoadedScript->HasSRI()) { 214 return false; 215 } 216 217 if (aStrategy.mHasSourceLengthMin) { 218 size_t len = JS::GetScriptSourceLength(aLoadedScript->GetStencil()); 219 if (len < aStrategy.mSourceLengthMin) { 220 return false; 221 } 222 } 223 224 if (aStrategy.mHasFetchCountMin) { 225 if (aLoadedScript->mFetchCount < aStrategy.mFetchCountMin) { 226 return false; 227 } 228 } 229 230 return true; 231 } 232 233 bool SharedScriptCache::MaybeScheduleUpdateDiskCache() { 234 auto strategy = ScriptLoader::GetDiskCacheStrategy(); 235 if (strategy.mIsDisabled) { 236 return false; 237 } 238 239 bool hasSaveable = false; 240 for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) { 241 JS::loader::LoadedScript* loadedScript = iter.Data().mResource; 242 if (ShouldSave(loadedScript, strategy)) { 243 hasSaveable = true; 244 break; 245 } 246 } 247 248 if (!hasSaveable) { 249 return false; 250 } 251 252 // TODO: Apply more flexible scheduling (bug 1902951) 253 254 nsCOMPtr<nsIRunnable> updater = 255 NewRunnableMethod("SharedScriptCache::UpdateDiskCache", this, 256 &SharedScriptCache::UpdateDiskCache); 257 (void)NS_DispatchToCurrentThreadQueue(updater.forget(), 258 EventQueuePriority::Idle); 259 return true; 260 } 261 262 class ScriptEncodeAndCompressionTask : public mozilla::Task { 263 public: 264 ScriptEncodeAndCompressionTask() 265 : Task(Kind::OffMainThreadOnly, EventQueuePriority::Idle) {} 266 virtual ~ScriptEncodeAndCompressionTask() = default; 267 268 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 269 bool GetName(nsACString& aName) override { 270 aName.AssignLiteral("ScriptEncodeAndCompressionTask"); 271 return true; 272 } 273 #endif 274 275 TaskResult Run() override { 276 SharedScriptCache::Get()->EncodeAndCompress(); 277 return TaskResult::Complete; 278 } 279 }; 280 281 class ScriptSaveTask : public mozilla::Task { 282 public: 283 ScriptSaveTask() : Task(Kind::MainThreadOnly, EventQueuePriority::Idle) {} 284 virtual ~ScriptSaveTask() = default; 285 286 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY 287 bool GetName(nsACString& aName) override { 288 aName.AssignLiteral("ScriptSaveTask"); 289 return true; 290 } 291 #endif 292 293 TaskResult Run() override { 294 SharedScriptCache::Get()->SaveToDiskCache(); 295 return TaskResult::Complete; 296 } 297 }; 298 299 void SharedScriptCache::UpdateDiskCache() { 300 auto strategy = ScriptLoader::GetDiskCacheStrategy(); 301 if (strategy.mIsDisabled) { 302 return; 303 } 304 305 mozilla::MutexAutoLock lock(mEncodeMutex); 306 307 if (!mEncodeItems.empty()) { 308 return; 309 } 310 311 for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) { 312 JS::loader::LoadedScript* loadedScript = iter.Data().mResource; 313 if (!ShouldSave(loadedScript, strategy)) { 314 continue; 315 } 316 317 if (!mEncodeItems.emplaceBack(loadedScript->GetStencil(), 318 std::move(loadedScript->SRI()), 319 loadedScript)) { 320 continue; 321 } 322 } 323 324 if (mEncodeItems.empty()) { 325 return; 326 } 327 328 RefPtr<ScriptEncodeAndCompressionTask> encodeTask = 329 new ScriptEncodeAndCompressionTask(); 330 RefPtr<ScriptSaveTask> saveTask = new ScriptSaveTask(); 331 saveTask->AddDependency(encodeTask); 332 333 TaskController::Get()->AddTask(encodeTask.forget()); 334 TaskController::Get()->AddTask(saveTask.forget()); 335 } 336 337 void SharedScriptCache::EncodeAndCompress() { 338 JS::FrontendContext* fc = JS::NewFrontendContext(); 339 if (!fc) { 340 return; 341 } 342 343 mozilla::MutexAutoLock lock(mEncodeMutex); 344 345 for (auto& item : mEncodeItems) { 346 if (!ScriptLoader::EncodeAndCompress(fc, item.mLoadedScript, item.mStencil, 347 item.mSRI, item.mCompressed)) { 348 item.mCompressed.clear(); 349 } 350 } 351 352 JS::DestroyFrontendContext(fc); 353 } 354 355 void SharedScriptCache::SaveToDiskCache() { 356 MOZ_ASSERT(NS_IsMainThread()); 357 358 mozilla::MutexAutoLock lock(mEncodeMutex); 359 360 for (const auto& item : mEncodeItems) { 361 if (item.mCompressed.empty()) { 362 item.mLoadedScript->DropDiskCacheReference(); 363 item.mLoadedScript->DropSRIOrSRIAndSerializedStencil(); 364 TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed"); 365 continue; 366 } 367 368 if (!ScriptLoader::SaveToDiskCache(item.mLoadedScript, item.mCompressed)) { 369 item.mLoadedScript->DropDiskCacheReference(); 370 item.mLoadedScript->DropSRIOrSRIAndSerializedStencil(); 371 TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed"); 372 } 373 374 item.mLoadedScript->DropDiskCacheReference(); 375 item.mLoadedScript->DropSRIOrSRIAndSerializedStencil(); 376 TRACE_FOR_TEST(item.mLoadedScript, "diskcache:saved"); 377 } 378 379 mEncodeItems.clear(); 380 } 381 382 } // namespace mozilla::dom