summaryrefslogtreecommitdiffstats
path: root/dom/xul/nsXULPrototypeCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xul/nsXULPrototypeCache.cpp')
-rw-r--r--dom/xul/nsXULPrototypeCache.cpp500
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
+}