diff options
Diffstat (limited to 'dom/xul/nsXULPrototypeCache.cpp')
-rw-r--r-- | dom/xul/nsXULPrototypeCache.cpp | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/dom/xul/nsXULPrototypeCache.cpp b/dom/xul/nsXULPrototypeCache.cpp new file mode 100644 index 0000000000..c70492b07c --- /dev/null +++ b/dom/xul/nsXULPrototypeCache.cpp @@ -0,0 +1,500 @@ +/* -*- 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 "nsXULPrototypeCache.h" + +#include "nsXULPrototypeDocument.h" +#include "nsIURI.h" +#include "nsNetUtil.h" + +#include "nsIFile.h" +#include "nsIMemoryReporter.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIObserverService.h" +#include "nsIStorageStream.h" + +#include "nsAppDirectoryServiceDefs.h" + +#include "js/experimental/JSStencil.h" +#include "js/TracingAPI.h" + +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_nglayout.h" +#include "mozilla/scache/StartupCache.h" +#include "mozilla/scache/StartupCacheUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/intl/LocaleService.h" + +using namespace mozilla; +using namespace mozilla::scache; +using mozilla::intl::LocaleService; + +static const char kXULCacheInfoKey[] = "nsXULPrototypeCache.startupCache"; +#define CACHE_PREFIX(aCompilationTarget) "xulcache/" aCompilationTarget + +static void DisableXULCacheChangedCallback(const char* aPref, void* aClosure) { + if (nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance()) { + if (!cache->IsEnabled()) { + // AbortCaching() calls Flush() for us. + cache->AbortCaching(); + } + } +} + +//---------------------------------------------------------------------- + +nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr; + +nsXULPrototypeCache::nsXULPrototypeCache() = default; + +NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver) + +/* static */ +nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() { + if (!sInstance) { + NS_ADDREF(sInstance = new nsXULPrototypeCache()); + + Preferences::RegisterCallback( + DisableXULCacheChangedCallback, + nsDependentCString( + StaticPrefs::GetPrefName_nglayout_debug_disable_xul_cache())); + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + nsXULPrototypeCache* p = sInstance; + obsSvc->AddObserver(p, "chrome-flush-caches", false); + obsSvc->AddObserver(p, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + obsSvc->AddObserver(p, "startupcache-invalidate", false); + } + } + return sInstance; +} + +//---------------------------------------------------------------------- + +NS_IMETHODIMP +nsXULPrototypeCache::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "chrome-flush-caches") || + !strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Flush(); + } else if (!strcmp(aTopic, "startupcache-invalidate")) { + AbortCaching(); + } else { + NS_WARNING("Unexpected observer topic."); + } + return NS_OK; +} + +nsXULPrototypeDocument* nsXULPrototypeCache::GetPrototype(nsIURI* aURI) { + if (!aURI) return nullptr; + + nsCOMPtr<nsIURI> uriWithoutRef; + NS_GetURIWithoutRef(aURI, getter_AddRefs(uriWithoutRef)); + + nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef); + if (protoDoc) { + return protoDoc; + } + + nsresult rv = BeginCaching(aURI); + if (NS_FAILED(rv)) return nullptr; + + // No prototype in XUL memory cache. Spin up the cache Service. + nsCOMPtr<nsIObjectInputStream> ois; + rv = GetPrototypeInputStream(aURI, getter_AddRefs(ois)); + if (NS_FAILED(rv)) { + return nullptr; + } + + RefPtr<nsXULPrototypeDocument> newProto; + rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto)); + if (NS_FAILED(rv)) return nullptr; + + rv = newProto->Read(ois); + if (NS_SUCCEEDED(rv)) { + rv = PutPrototype(newProto); + } else { + newProto = nullptr; + } + + mInputStreamTable.Remove(aURI); + return newProto; +} + +nsresult nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument) { + if (!aDocument->GetURI()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> uri; + NS_GetURIWithoutRef(aDocument->GetURI(), getter_AddRefs(uri)); + + // Put() releases any old value + mPrototypeTable.InsertOrUpdate(uri, RefPtr{aDocument}); + + return NS_OK; +} + +JS::Stencil* nsXULPrototypeCache::GetStencil(nsIURI* aURI) { + if (auto* entry = mStencilTable.GetEntry(aURI)) { + return entry->mStencil; + } + return nullptr; +} + +nsresult nsXULPrototypeCache::PutStencil(nsIURI* aURI, JS::Stencil* aStencil) { + MOZ_ASSERT(aStencil, "Need a non-NULL stencil"); + +#ifdef DEBUG_BUG_392650 + if (mStencilTable.Get(aURI)) { + nsAutoCString scriptName; + aURI->GetSpec(scriptName); + nsAutoCString message("Loaded script "); + message += scriptName; + message += " twice (bug 392650)"; + NS_WARNING(message.get()); + } +#endif + + mStencilTable.PutEntry(aURI)->mStencil = aStencil; + + return NS_OK; +} + +void nsXULPrototypeCache::Flush() { + mPrototypeTable.Clear(); + mStencilTable.Clear(); +} + +bool nsXULPrototypeCache::IsEnabled() { + return !StaticPrefs::nglayout_debug_disable_xul_cache(); +} + +void nsXULPrototypeCache::AbortCaching() { + // Flush the XUL cache for good measure, in case we cached a bogus/downrev + // script, somehow. + Flush(); + + // Clear the cache set + mStartupCacheURITable.Clear(); +} + +nsresult nsXULPrototypeCache::WritePrototype( + nsXULPrototypeDocument* aPrototypeDocument) { + nsresult rv = NS_OK, rv2 = NS_OK; + + if (!StartupCache::GetSingleton()) return NS_OK; + + nsCOMPtr<nsIURI> protoURI = aPrototypeDocument->GetURI(); + + nsCOMPtr<nsIObjectOutputStream> oos; + rv = GetPrototypeOutputStream(protoURI, getter_AddRefs(oos)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aPrototypeDocument->Write(oos); + NS_ENSURE_SUCCESS(rv, rv); + FinishPrototypeOutputStream(protoURI); + return NS_FAILED(rv) ? rv : rv2; +} + +static nsresult PathifyURIForType(nsXULPrototypeCache::CacheType cacheType, + nsIURI* in, nsACString& out) { + switch (cacheType) { + case nsXULPrototypeCache::CacheType::Prototype: + return PathifyURI(CACHE_PREFIX("proto"), in, out); + case nsXULPrototypeCache::CacheType::Script: + return PathifyURI(CACHE_PREFIX("script"), in, out); + } + MOZ_ASSERT_UNREACHABLE("unknown cache type?"); + return NS_ERROR_UNEXPECTED; +} + +nsresult nsXULPrototypeCache::GetInputStream(CacheType cacheType, nsIURI* uri, + nsIObjectInputStream** stream) { + nsAutoCString spec; + nsresult rv = PathifyURIForType(cacheType, uri, spec); + if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; + + const char* buf; + uint32_t len; + nsCOMPtr<nsIObjectInputStream> ois; + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) return NS_ERROR_NOT_AVAILABLE; + + rv = sc->GetBuffer(spec.get(), &buf, &len); + if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; + + rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois)); + NS_ENSURE_SUCCESS(rv, rv); + + mInputStreamTable.InsertOrUpdate(uri, ois); + + ois.forget(stream); + return NS_OK; +} + +nsresult nsXULPrototypeCache::FinishInputStream(nsIURI* uri) { + mInputStreamTable.Remove(uri); + return NS_OK; +} + +nsresult nsXULPrototypeCache::GetOutputStream(nsIURI* uri, + nsIObjectOutputStream** stream) { + nsresult rv; + nsCOMPtr<nsIObjectOutputStream> objectOutput; + nsCOMPtr<nsIStorageStream> storageStream; + bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream)); + if (found) { + // Setting an output stream here causes crashes on Windows. The previous + // version of this code always returned NS_ERROR_OUT_OF_MEMORY here, + // because it used a mistyped contract ID to create its object stream. + return NS_ERROR_NOT_IMPLEMENTED; +#if 0 + nsCOMPtr<nsIOutputStream> outputStream + = do_QueryInterface(storageStream); + objectOutput = NS_NewObjectOutputStream(outputStream); +#endif + } else { + rv = NewObjectOutputWrappedStorageStream( + getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false); + NS_ENSURE_SUCCESS(rv, rv); + mOutputStreamTable.InsertOrUpdate(uri, storageStream); + } + objectOutput.forget(stream); + return NS_OK; +} + +nsresult nsXULPrototypeCache::FinishOutputStream(CacheType cacheType, + nsIURI* uri) { + nsresult rv; + StartupCache* sc = StartupCache::GetSingleton(); + if (!sc) return NS_ERROR_NOT_AVAILABLE; + + nsCOMPtr<nsIStorageStream> storageStream; + bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream)); + if (!found) return NS_ERROR_UNEXPECTED; + nsCOMPtr<nsIOutputStream> outputStream = do_QueryInterface(storageStream); + outputStream->Close(); + + UniqueFreePtr<char[]> buf; + uint32_t len; + rv = NewBufferFromStorageStream(storageStream, &buf, &len); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mStartupCacheURITable.GetEntry(uri)) { + nsAutoCString spec; + rv = PathifyURIForType(cacheType, uri, spec); + if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE; + rv = sc->PutBuffer(spec.get(), std::move(buf), len); + if (NS_SUCCEEDED(rv)) { + mOutputStreamTable.Remove(uri); + mStartupCacheURITable.PutEntry(uri); + } + } + + return rv; +} + +// We have data if we're in the middle of writing it or we already +// have it in the cache. +nsresult nsXULPrototypeCache::HasData(CacheType cacheType, nsIURI* uri, + bool* exists) { + if (mOutputStreamTable.Get(uri, nullptr)) { + *exists = true; + return NS_OK; + } + nsAutoCString spec; + nsresult rv = PathifyURIForType(cacheType, uri, spec); + if (NS_FAILED(rv)) { + *exists = false; + return NS_OK; + } + UniquePtr<char[]> buf; + StartupCache* sc = StartupCache::GetSingleton(); + if (sc) { + *exists = sc->HasEntry(spec.get()); + } else { + *exists = false; + } + return NS_OK; +} + +nsresult nsXULPrototypeCache::BeginCaching(nsIURI* aURI) { + nsresult rv, tmp; + + nsAutoCString path; + aURI->GetPathQueryRef(path); + if (!(StringEndsWith(path, ".xul"_ns) || StringEndsWith(path, ".xhtml"_ns))) { + return NS_ERROR_NOT_AVAILABLE; + } + + StartupCache* startupCache = StartupCache::GetSingleton(); + if (!startupCache) return NS_ERROR_FAILURE; + + if (StaticPrefs::nglayout_debug_disable_xul_cache()) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Get the chrome directory to validate against the one stored in the + // cache file, or to store there if we're generating a new file. + nsCOMPtr<nsIFile> chromeDir; + rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, getter_AddRefs(chromeDir)); + if (NS_FAILED(rv)) return rv; + nsAutoCString chromePath; + rv = chromeDir->GetPersistentDescriptor(chromePath); + if (NS_FAILED(rv)) return rv; + + // XXXbe we assume the first package's locale is the same as the locale of + // all subsequent packages of cached chrome URIs.... + nsAutoCString package; + rv = aURI->GetHost(package); + if (NS_FAILED(rv)) return rv; + nsAutoCString locale; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale); + + nsAutoCString fileChromePath, fileLocale; + + const char* buf = nullptr; + uint32_t len, amtRead; + nsCOMPtr<nsIObjectInputStream> objectInput; + + rv = startupCache->GetBuffer(kXULCacheInfoKey, &buf, &len); + if (NS_SUCCEEDED(rv)) + rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput)); + + if (NS_SUCCEEDED(rv)) { + rv = objectInput->ReadCString(fileLocale); + tmp = objectInput->ReadCString(fileChromePath); + if (NS_FAILED(tmp)) { + rv = tmp; + } + if (NS_FAILED(rv) || + (!fileChromePath.Equals(chromePath) || !fileLocale.Equals(locale))) { + // Our cache won't be valid in this case, we'll need to rewrite. + // XXX This blows away work that other consumers (like + // mozJSModuleLoader) have done, need more fine-grained control. + startupCache->InvalidateCache(); + mStartupCacheURITable.Clear(); + rv = NS_ERROR_UNEXPECTED; + } + } else if (rv != NS_ERROR_NOT_AVAILABLE) + // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile. + return rv; + + if (NS_FAILED(rv)) { + // Either the cache entry was invalid or it didn't exist, so write it now. + nsCOMPtr<nsIObjectOutputStream> objectOutput; + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIStorageStream> storageStream; + rv = NewObjectOutputWrappedStorageStream( + getter_AddRefs(objectOutput), getter_AddRefs(storageStream), false); + if (NS_SUCCEEDED(rv)) { + rv = objectOutput->WriteStringZ(locale.get()); + tmp = objectOutput->WriteStringZ(chromePath.get()); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = objectOutput->Close(); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream)); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + + if (NS_SUCCEEDED(rv)) { + uint64_t len64; + rv = inputStream->Available(&len64); + if (NS_SUCCEEDED(rv)) { + if (len64 <= UINT32_MAX) + len = (uint32_t)len64; + else + rv = NS_ERROR_FILE_TOO_BIG; + } + } + + if (NS_SUCCEEDED(rv)) { + auto putBuf = UniqueFreePtr<char[]>( + reinterpret_cast<char*>(malloc(sizeof(char) * len))); + rv = inputStream->Read(putBuf.get(), len, &amtRead); + if (NS_SUCCEEDED(rv) && len == amtRead) + rv = startupCache->PutBuffer(kXULCacheInfoKey, std::move(putBuf), len); + else { + rv = NS_ERROR_UNEXPECTED; + } + } + + // Failed again, just bail. + if (NS_FAILED(rv)) { + startupCache->InvalidateCache(); + mStartupCacheURITable.Clear(); + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration) { + for (const auto& prototype : mPrototypeTable.Values()) { + prototype->MarkInCCGeneration(aGeneration); + } +} + +MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf) + +static void ReportSize(const nsCString& aPath, size_t aAmount, + const nsCString& aDescription, + nsIHandleReportCallback* aHandleReport, + nsISupports* aData) { + nsAutoCString path("explicit/xul-prototype-cache/"); + path += aPath; + aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP, + nsIMemoryReporter::UNITS_BYTES, aAmount, aDescription, + aData); +} + +/* static */ +void nsXULPrototypeCache::CollectMemoryReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData) { + if (!sInstance) { + return; + } + + MallocSizeOf mallocSizeOf = CacheMallocSizeOf; + size_t other = mallocSizeOf(sInstance); + +#define REPORT_SIZE(_path, _amount, _desc) \ + ReportSize(_path, _amount, nsLiteralCString(_desc), aHandleReport, aData) + + other += sInstance->mPrototypeTable.ShallowSizeOfExcludingThis(mallocSizeOf); + // TODO Report content in mPrototypeTable? + + other += sInstance->mStencilTable.ShallowSizeOfExcludingThis(mallocSizeOf); + // TODO Report content inside mStencilTable? + + other += + sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf); + + other += + sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf); + other += + sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf); + + REPORT_SIZE("other"_ns, other, + "Memory used by " + "the instance and tables of the XUL prototype cache."); + +#undef REPORT_SIZE +} |