nsXULPrototypeCache.cpp (15441B)
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 "nsXULPrototypeCache.h" 8 9 #include "js/TracingAPI.h" 10 #include "js/experimental/JSStencil.h" 11 #include "mozilla/Preferences.h" 12 #include "mozilla/RefPtr.h" 13 #include "mozilla/StaticPrefs_nglayout.h" 14 #include "mozilla/StyleSheetInlines.h" 15 #include "mozilla/UniquePtrExtensions.h" 16 #include "mozilla/intl/LocaleService.h" 17 #include "mozilla/scache/StartupCache.h" 18 #include "mozilla/scache/StartupCacheUtils.h" 19 #include "nsAppDirectoryServiceDefs.h" 20 #include "nsIFile.h" 21 #include "nsIMemoryReporter.h" 22 #include "nsIObjectInputStream.h" 23 #include "nsIObjectOutputStream.h" 24 #include "nsIObserverService.h" 25 #include "nsIStorageStream.h" 26 #include "nsIURI.h" 27 #include "nsNetUtil.h" 28 #include "nsXULPrototypeDocument.h" 29 30 using namespace mozilla; 31 using namespace mozilla::scache; 32 using mozilla::intl::LocaleService; 33 34 static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache"; 35 #define CACHE_PREFIX(aCompilationTarget) "xulcache/" aCompilationTarget 36 37 static void DisableXULCacheChangedCallback(const char* aPref, void* aClosure) { 38 if (nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance()) { 39 if (!cache->IsEnabled()) { 40 // AbortCaching() calls Flush() for us. 41 cache->AbortCaching(); 42 } 43 } 44 } 45 46 //---------------------------------------------------------------------- 47 48 nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr; 49 50 nsXULPrototypeCache::nsXULPrototypeCache() = default; 51 52 NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver) 53 54 /* static */ 55 nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() { 56 if (!sInstance) { 57 NS_ADDREF(sInstance = new nsXULPrototypeCache()); 58 59 Preferences::RegisterCallback( 60 DisableXULCacheChangedCallback, 61 nsDependentCString( 62 StaticPrefs::GetPrefName_nglayout_debug_disable_xul_cache())); 63 64 nsCOMPtr<nsIObserverService> obsSvc = 65 mozilla::services::GetObserverService(); 66 if (obsSvc) { 67 nsXULPrototypeCache* p = sInstance; 68 obsSvc->AddObserver(p, "chrome-flush-caches", false); 69 obsSvc->AddObserver(p, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); 70 obsSvc->AddObserver(p, "startupcache-invalidate", false); 71 } 72 } 73 return sInstance; 74 } 75 76 //---------------------------------------------------------------------- 77 78 NS_IMETHODIMP 79 nsXULPrototypeCache::Observe(nsISupports* aSubject, const char* aTopic, 80 const char16_t* aData) { 81 if (!strcmp(aTopic, "chrome-flush-caches") || 82 !strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { 83 Flush(); 84 } else if (!strcmp(aTopic, "startupcache-invalidate")) { 85 AbortCaching(); 86 } else { 87 NS_WARNING("Unexpected observer topic."); 88 } 89 return NS_OK; 90 } 91 92 nsXULPrototypeDocument* nsXULPrototypeCache::GetPrototype(nsIURI* aURI) { 93 if (!aURI) return nullptr; 94 95 nsCOMPtr<nsIURI> uriWithoutRef; 96 NS_GetURIWithoutRef(aURI, getter_AddRefs(uriWithoutRef)); 97 98 nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef); 99 if (protoDoc) { 100 return protoDoc; 101 } 102 103 nsresult rv = BeginCaching(aURI); 104 if (NS_FAILED(rv)) return nullptr; 105 106 // No prototype in XUL memory cache. Spin up the cache Service. 107 nsCOMPtr<nsIObjectInputStream> ois; 108 rv = GetPrototypeInputStream(aURI, getter_AddRefs(ois)); 109 if (NS_FAILED(rv)) { 110 return nullptr; 111 } 112 113 RefPtr<nsXULPrototypeDocument> newProto; 114 rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto)); 115 if (NS_FAILED(rv)) return nullptr; 116 117 rv = newProto->Read(ois); 118 if (NS_SUCCEEDED(rv)) { 119 rv = PutPrototype(newProto); 120 } else { 121 newProto = nullptr; 122 } 123 124 mInputStreamTable.Remove(aURI); 125 return newProto; 126 } 127 128 nsresult nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument) { 129 if (!aDocument->GetURI()) { 130 return NS_ERROR_FAILURE; 131 } 132 133 nsCOMPtr<nsIURI> uri; 134 NS_GetURIWithoutRef(aDocument->GetURI(), getter_AddRefs(uri)); 135 136 // Put() releases any old value 137 mPrototypeTable.InsertOrUpdate(uri, RefPtr{aDocument}); 138 139 return NS_OK; 140 } 141 142 JS::Stencil* nsXULPrototypeCache::GetStencil(nsIURI* aURI) { 143 if (auto* entry = mStencilTable.GetEntry(aURI)) { 144 return entry->mStencil; 145 } 146 return nullptr; 147 } 148 149 nsresult nsXULPrototypeCache::PutStencil(nsIURI* aURI, JS::Stencil* aStencil) { 150 MOZ_ASSERT(aStencil, "Need a non-NULL stencil"); 151 152 #ifdef DEBUG_BUG_392650 153 if (mStencilTable.Get(aURI)) { 154 nsAutoCString scriptName; 155 aURI->GetSpec(scriptName); 156 nsAutoCString message("Loaded script "); 157 message += scriptName; 158 message += " twice (bug 392650)"; 159 NS_WARNING(message.get()); 160 } 161 #endif 162 163 mStencilTable.PutEntry(aURI)->mStencil = aStencil; 164 165 return NS_OK; 166 } 167 168 void nsXULPrototypeCache::Flush() { 169 mPrototypeTable.Clear(); 170 mStencilTable.Clear(); 171 } 172 173 bool nsXULPrototypeCache::IsEnabled() { 174 return !StaticPrefs::nglayout_debug_disable_xul_cache(); 175 } 176 177 void nsXULPrototypeCache::AbortCaching() { 178 // Flush the XUL cache for good measure, in case we cached a bogus/downrev 179 // script, somehow. 180 Flush(); 181 182 // Clear the cache set 183 mStartupCacheURITable.Clear(); 184 } 185 186 nsresult nsXULPrototypeCache::WritePrototype( 187 nsXULPrototypeDocument* aPrototypeDocument) { 188 nsresult rv = NS_OK, rv2 = NS_OK; 189 190 if (!StartupCache::GetSingleton()) return NS_OK; 191 192 nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI(); 193 194 nsCOMPtr<nsIObjectOutputStream> oos; 195 rv = GetPrototypeOutputStream(protoURI, getter_AddRefs(oos)); 196 NS_ENSURE_SUCCESS(rv, rv); 197 198 rv = aPrototypeDocument->Write(oos); 199 NS_ENSURE_SUCCESS(rv, rv); 200 FinishPrototypeOutputStream(protoURI); 201 return NS_FAILED(rv) ? rv : rv2; 202 } 203 204 static nsresult PathifyURIForType(nsXULPrototypeCache::CacheType cacheType, 205 nsIURI* in, nsACString& out) { 206 switch (cacheType) { 207 case nsXULPrototypeCache::CacheType::Prototype: 208 return PathifyURI(CACHE_PREFIX("proto"), in, out); 209 case nsXULPrototypeCache::CacheType::Script: 210 return PathifyURI(CACHE_PREFIX("script"), in, out); 211 } 212 MOZ_ASSERT_UNREACHABLE("unknown cache type?"); 213 return NS_ERROR_UNEXPECTED; 214 } 215 216 nsresult nsXULPrototypeCache::GetInputStream(CacheType cacheType, nsIURI* uri, 217 nsIObjectInputStream** stream) { 218 nsAutoCString spec; 219 nsresult rv = PathifyURIForType(cacheType, uri, spec); 220 if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; 221 222 const char* buf; 223 uint32_t len; 224 nsCOMPtr<nsIObjectInputStream> ois; 225 StartupCache* sc = StartupCache::GetSingleton(); 226 if (!sc) return NS_ERROR_NOT_AVAILABLE; 227 228 rv = sc->GetBuffer(spec.get(), &buf, &len); 229 if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; 230 231 rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois)); 232 NS_ENSURE_SUCCESS(rv, rv); 233 234 mInputStreamTable.InsertOrUpdate(uri, ois); 235 236 ois.forget(stream); 237 return NS_OK; 238 } 239 240 nsresult nsXULPrototypeCache::FinishInputStream(nsIURI* uri) { 241 mInputStreamTable.Remove(uri); 242 return NS_OK; 243 } 244 245 nsresult nsXULPrototypeCache::GetOutputStream(nsIURI* uri, 246 nsIObjectOutputStream** stream) { 247 nsresult rv; 248 nsCOMPtr<nsIObjectOutputStream> objectOutput; 249 nsCOMPtr<nsIStorageStream> storageStream; 250 bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream)); 251 if (found) { 252 // Setting an output stream here causes crashes on Windows. The previous 253 // version of this code always returned NS_ERROR_OUT_OF_MEMORY here, 254 // because it used a mistyped contract ID to create its object stream. 255 return NS_ERROR_NOT_IMPLEMENTED; 256 #if 0 257 nsCOMPtr<nsIOutputStream> outputStream 258 = do_QueryInterface(storageStream); 259 objectOutput = NS_NewObjectOutputStream(outputStream); 260 #endif 261 } else { 262 rv = NewObjectOutputWrappedStorageStream( 263 getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false); 264 NS_ENSURE_SUCCESS(rv, rv); 265 mOutputStreamTable.InsertOrUpdate(uri, storageStream); 266 } 267 objectOutput.forget(stream); 268 return NS_OK; 269 } 270 271 nsresult nsXULPrototypeCache::FinishOutputStream(CacheType cacheType, 272 nsIURI* uri) { 273 nsresult rv; 274 StartupCache* sc = StartupCache::GetSingleton(); 275 if (!sc) return NS_ERROR_NOT_AVAILABLE; 276 277 nsCOMPtr<nsIStorageStream> storageStream; 278 bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream)); 279 if (!found) return NS_ERROR_UNEXPECTED; 280 nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream); 281 outputStream->Close(); 282 283 UniqueFreePtr<char[]> buf; 284 uint32_t len; 285 rv = NewBufferFromStorageStream(storageStream, &buf, &len); 286 NS_ENSURE_SUCCESS(rv, rv); 287 288 if (!mStartupCacheURITable.GetEntry(uri)) { 289 nsAutoCString spec; 290 rv = PathifyURIForType(cacheType, uri, spec); 291 if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; 292 rv = sc->PutBuffer(spec.get(), std::move(buf), len); 293 if (NS_SUCCEEDED(rv)) { 294 mOutputStreamTable.Remove(uri); 295 mStartupCacheURITable.PutEntry(uri); 296 } 297 } 298 299 return rv; 300 } 301 302 // We have data if we're in the middle of writing it or we already 303 // have it in the cache. 304 nsresult nsXULPrototypeCache::HasData(CacheType cacheType, nsIURI* uri, 305 bool* exists) { 306 if (mOutputStreamTable.Get(uri, nullptr)) { 307 *exists = true; 308 return NS_OK; 309 } 310 nsAutoCString spec; 311 nsresult rv = PathifyURIForType(cacheType, uri, spec); 312 if (NS_FAILED(rv)) { 313 *exists = false; 314 return NS_OK; 315 } 316 UniquePtr<char[]> buf; 317 StartupCache* sc = StartupCache::GetSingleton(); 318 if (sc) { 319 *exists = sc->HasEntry(spec.get()); 320 } else { 321 *exists = false; 322 } 323 return NS_OK; 324 } 325 326 nsresult nsXULPrototypeCache::BeginCaching(nsIURI* aURI) { 327 nsresult rv, tmp; 328 329 nsAutoCString path; 330 aURI->GetPathQueryRef(path); 331 if (!(StringEndsWith(path, ".xul"_ns) || StringEndsWith(path, ".xhtml"_ns))) { 332 return NS_ERROR_NOT_AVAILABLE; 333 } 334 335 StartupCache* startupCache = StartupCache::GetSingleton(); 336 if (!startupCache) return NS_ERROR_FAILURE; 337 338 if (StaticPrefs::nglayout_debug_disable_xul_cache()) { 339 return NS_ERROR_NOT_AVAILABLE; 340 } 341 342 // Get the chrome directory to validate against the one stored in the 343 // cache file, or to store there if we're generating a new file. 344 nsCOMPtr<nsIFile> chromeDir; 345 rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir)); 346 if (NS_FAILED(rv)) return rv; 347 nsAutoCString chromePath; 348 rv = chromeDir->GetPersistentDescriptor(chromePath); 349 if (NS_FAILED(rv)) return rv; 350 351 // XXXbe we assume the first package's locale is the same as the locale of 352 // all subsequent packages of cached chrome URIs.... 353 nsAutoCString package; 354 rv = aURI->GetHost(package); 355 if (NS_FAILED(rv)) return rv; 356 nsAutoCString locale; 357 LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale); 358 359 nsAutoCString fileChromePath, fileLocale; 360 361 const char* buf = nullptr; 362 uint32_t len, amtRead; 363 nsCOMPtr<nsIObjectInputStream> objectInput; 364 365 rv = startupCache->GetBuffer(kXULCacheInfoKey, &buf, &len); 366 if (NS_SUCCEEDED(rv)) 367 rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput)); 368 369 if (NS_SUCCEEDED(rv)) { 370 rv = objectInput->ReadCString(fileLocale); 371 tmp = objectInput->ReadCString(fileChromePath); 372 if (NS_FAILED(tmp)) { 373 rv = tmp; 374 } 375 if (NS_FAILED(rv) || 376 (!fileChromePath.Equals(chromePath) || !fileLocale.Equals(locale))) { 377 // Our cache won't be valid in this case, we'll need to rewrite. 378 // XXX This blows away work that other consumers (like 379 // mozJSModuleLoader) have done, need more fine-grained control. 380 startupCache->InvalidateCache(); 381 mStartupCacheURITable.Clear(); 382 rv = NS_ERROR_UNEXPECTED; 383 } 384 } else if (rv != NS_ERROR_NOT_AVAILABLE) 385 // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile. 386 return rv; 387 388 if (NS_FAILED(rv)) { 389 // Either the cache entry was invalid or it didn't exist, so write it now. 390 nsCOMPtr<nsIObjectOutputStream> objectOutput; 391 nsCOMPtr<nsIInputStream> inputStream; 392 nsCOMPtr<nsIStorageStream> storageStream; 393 rv = NewObjectOutputWrappedStorageStream( 394 getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false); 395 if (NS_SUCCEEDED(rv)) { 396 rv = objectOutput->WriteStringZ(locale.get()); 397 tmp = objectOutput->WriteStringZ(chromePath.get()); 398 if (NS_FAILED(tmp)) { 399 rv = tmp; 400 } 401 tmp = objectOutput->Close(); 402 if (NS_FAILED(tmp)) { 403 rv = tmp; 404 } 405 tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream)); 406 if (NS_FAILED(tmp)) { 407 rv = tmp; 408 } 409 } 410 411 if (NS_SUCCEEDED(rv)) { 412 uint64_t len64; 413 rv = inputStream->Available(&len64); 414 if (NS_SUCCEEDED(rv)) { 415 if (len64 <= UINT32_MAX) 416 len = (uint32_t)len64; 417 else 418 rv = NS_ERROR_FILE_TOO_BIG; 419 } 420 } 421 422 if (NS_SUCCEEDED(rv)) { 423 auto putBuf = UniqueFreePtr<char[]>( 424 reinterpret_cast<char*>(malloc(sizeof(char) * len))); 425 rv = inputStream->Read(putBuf.get(), len, &amtRead); 426 if (NS_SUCCEEDED(rv) && len == amtRead) 427 rv = startupCache->PutBuffer(kXULCacheInfoKey, std::move(putBuf), len); 428 else { 429 rv = NS_ERROR_UNEXPECTED; 430 } 431 } 432 433 // Failed again, just bail. 434 if (NS_FAILED(rv)) { 435 startupCache->InvalidateCache(); 436 mStartupCacheURITable.Clear(); 437 return NS_ERROR_FAILURE; 438 } 439 } 440 441 return NS_OK; 442 } 443 444 void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration) { 445 for (const auto& prototype : mPrototypeTable.Values()) { 446 prototype->MarkInCCGeneration(aGeneration); 447 } 448 } 449 450 MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf) 451 452 static void ReportSize(const nsCString& aPath, size_t aAmount, 453 const nsCString& aDescription, 454 nsIHandleReportCallback* aHandleReport, 455 nsISupports* aData) { 456 nsAutoCString path("explicit/xul-prototype-cache/"); 457 path += aPath; 458 aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP, 459 nsIMemoryReporter::UNITS_BYTES, aAmount, aDescription, 460 aData); 461 } 462 463 /* static */ 464 void nsXULPrototypeCache::CollectMemoryReports( 465 nsIHandleReportCallback* aHandleReport, nsISupports* aData) { 466 if (!sInstance) { 467 return; 468 } 469 470 MallocSizeOf mallocSizeOf = CacheMallocSizeOf; 471 size_t other = mallocSizeOf(sInstance); 472 473 #define REPORT_SIZE(_path, _amount, _desc) \ 474 ReportSize(_path, _amount, nsLiteralCString(_desc), aHandleReport, aData) 475 476 other += sInstance->mPrototypeTable.ShallowSizeOfExcludingThis(mallocSizeOf); 477 // TODO Report content in mPrototypeTable? 478 479 other += sInstance->mStencilTable.ShallowSizeOfExcludingThis(mallocSizeOf); 480 // TODO Report content inside mStencilTable? 481 482 other += 483 sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf); 484 485 other += 486 sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf); 487 other += 488 sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf); 489 490 REPORT_SIZE("other"_ns, other, 491 "Memory used by " 492 "the instance and tables of the XUL prototype cache."); 493 494 #undef REPORT_SIZE 495 }