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/. */
7 #include "nsXULPrototypeCache.h"
10 #include "nsXULPrototypeDocument.h"
12 #include "nsNetUtil.h"
15 #include "nsIMemoryReporter.h"
16 #include "nsIObjectInputStream.h"
17 #include "nsIObjectOutputStream.h"
18 #include "nsIObserverService.h"
19 #include "nsIStorageStream.h"
21 #include "nsAppDirectoryServiceDefs.h"
23 #include "js/experimental/JSStencil.h"
24 #include "js/TracingAPI.h"
26 #include "mozilla/StyleSheetInlines.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/StaticPrefs_nglayout.h"
29 #include "mozilla/scache/StartupCache.h"
30 #include "mozilla/scache/StartupCacheUtils.h"
31 #include "mozilla/Telemetry.h"
32 #include "mozilla/RefPtr.h"
33 #include "mozilla/intl/LocaleService.h"
35 using namespace mozilla
;
36 using namespace mozilla::scache
;
37 using mozilla::intl::LocaleService
;
39 static const char kXULCacheInfoKey
[] = "nsXULPrototypeCache.startupCache";
40 #define CACHE_PREFIX(aCompilationTarget) "xulcache/" aCompilationTarget
42 static void DisableXULCacheChangedCallback(const char* aPref
, void* aClosure
) {
43 if (nsXULPrototypeCache
* cache
= nsXULPrototypeCache::GetInstance()) {
44 if (!cache
->IsEnabled()) {
45 // AbortCaching() calls Flush() for us.
46 cache
->AbortCaching();
51 //----------------------------------------------------------------------
53 nsXULPrototypeCache
* nsXULPrototypeCache::sInstance
= nullptr;
55 nsXULPrototypeCache::nsXULPrototypeCache() = default;
57 NS_IMPL_ISUPPORTS(nsXULPrototypeCache
, nsIObserver
)
60 nsXULPrototypeCache
* nsXULPrototypeCache::GetInstance() {
62 NS_ADDREF(sInstance
= new nsXULPrototypeCache());
64 Preferences::RegisterCallback(
65 DisableXULCacheChangedCallback
,
67 StaticPrefs::GetPrefName_nglayout_debug_disable_xul_cache()));
69 nsCOMPtr
<nsIObserverService
> obsSvc
=
70 mozilla::services::GetObserverService();
72 nsXULPrototypeCache
* p
= sInstance
;
73 obsSvc
->AddObserver(p
, "chrome-flush-caches", false);
74 obsSvc
->AddObserver(p
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
, false);
75 obsSvc
->AddObserver(p
, "startupcache-invalidate", false);
81 //----------------------------------------------------------------------
84 nsXULPrototypeCache::Observe(nsISupports
* aSubject
, const char* aTopic
,
85 const char16_t
* aData
) {
86 if (!strcmp(aTopic
, "chrome-flush-caches") ||
87 !strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
)) {
89 } else if (!strcmp(aTopic
, "startupcache-invalidate")) {
92 NS_WARNING("Unexpected observer topic.");
97 nsXULPrototypeDocument
* nsXULPrototypeCache::GetPrototype(nsIURI
* aURI
) {
98 if (!aURI
) return nullptr;
100 nsCOMPtr
<nsIURI
> uriWithoutRef
;
101 NS_GetURIWithoutRef(aURI
, getter_AddRefs(uriWithoutRef
));
103 nsXULPrototypeDocument
* protoDoc
= mPrototypeTable
.GetWeak(uriWithoutRef
);
108 nsresult rv
= BeginCaching(aURI
);
109 if (NS_FAILED(rv
)) return nullptr;
111 // No prototype in XUL memory cache. Spin up the cache Service.
112 nsCOMPtr
<nsIObjectInputStream
> ois
;
113 rv
= GetPrototypeInputStream(aURI
, getter_AddRefs(ois
));
118 RefPtr
<nsXULPrototypeDocument
> newProto
;
119 rv
= NS_NewXULPrototypeDocument(getter_AddRefs(newProto
));
120 if (NS_FAILED(rv
)) return nullptr;
122 rv
= newProto
->Read(ois
);
123 if (NS_SUCCEEDED(rv
)) {
124 rv
= PutPrototype(newProto
);
129 mInputStreamTable
.Remove(aURI
);
133 nsresult
nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument
* aDocument
) {
134 if (!aDocument
->GetURI()) {
135 return NS_ERROR_FAILURE
;
138 nsCOMPtr
<nsIURI
> uri
;
139 NS_GetURIWithoutRef(aDocument
->GetURI(), getter_AddRefs(uri
));
141 // Put() releases any old value
142 mPrototypeTable
.InsertOrUpdate(uri
, RefPtr
{aDocument
});
147 JS::Stencil
* nsXULPrototypeCache::GetStencil(nsIURI
* aURI
) {
148 if (auto* entry
= mStencilTable
.GetEntry(aURI
)) {
149 return entry
->mStencil
;
154 nsresult
nsXULPrototypeCache::PutStencil(nsIURI
* aURI
, JS::Stencil
* aStencil
) {
155 MOZ_ASSERT(aStencil
, "Need a non-NULL stencil");
157 #ifdef DEBUG_BUG_392650
158 if (mStencilTable
.Get(aURI
)) {
159 nsAutoCString scriptName
;
160 aURI
->GetSpec(scriptName
);
161 nsAutoCString
message("Loaded script ");
162 message
+= scriptName
;
163 message
+= " twice (bug 392650)";
164 NS_WARNING(message
.get());
168 mStencilTable
.PutEntry(aURI
)->mStencil
= aStencil
;
173 void nsXULPrototypeCache::Flush() {
174 mPrototypeTable
.Clear();
175 mStencilTable
.Clear();
178 bool nsXULPrototypeCache::IsEnabled() {
179 return !StaticPrefs::nglayout_debug_disable_xul_cache();
182 void nsXULPrototypeCache::AbortCaching() {
183 // Flush the XUL cache for good measure, in case we cached a bogus/downrev
187 // Clear the cache set
188 mStartupCacheURITable
.Clear();
191 nsresult
nsXULPrototypeCache::WritePrototype(
192 nsXULPrototypeDocument
* aPrototypeDocument
) {
193 nsresult rv
= NS_OK
, rv2
= NS_OK
;
195 if (!StartupCache::GetSingleton()) return NS_OK
;
197 nsCOMPtr
<nsIURI
> protoURI
= aPrototypeDocument
->GetURI();
199 nsCOMPtr
<nsIObjectOutputStream
> oos
;
200 rv
= GetPrototypeOutputStream(protoURI
, getter_AddRefs(oos
));
201 NS_ENSURE_SUCCESS(rv
, rv
);
203 rv
= aPrototypeDocument
->Write(oos
);
204 NS_ENSURE_SUCCESS(rv
, rv
);
205 FinishPrototypeOutputStream(protoURI
);
206 return NS_FAILED(rv
) ? rv
: rv2
;
209 static nsresult
PathifyURIForType(nsXULPrototypeCache::CacheType cacheType
,
210 nsIURI
* in
, nsACString
& out
) {
212 case nsXULPrototypeCache::CacheType::Prototype
:
213 return PathifyURI(CACHE_PREFIX("proto"), in
, out
);
214 case nsXULPrototypeCache::CacheType::Script
:
215 return PathifyURI(CACHE_PREFIX("script"), in
, out
);
217 MOZ_ASSERT_UNREACHABLE("unknown cache type?");
218 return NS_ERROR_UNEXPECTED
;
221 nsresult
nsXULPrototypeCache::GetInputStream(CacheType cacheType
, nsIURI
* uri
,
222 nsIObjectInputStream
** stream
) {
224 nsresult rv
= PathifyURIForType(cacheType
, uri
, spec
);
225 if (NS_FAILED(rv
)) return NS_ERROR_NOT_AVAILABLE
;
229 nsCOMPtr
<nsIObjectInputStream
> ois
;
230 StartupCache
* sc
= StartupCache::GetSingleton();
231 if (!sc
) return NS_ERROR_NOT_AVAILABLE
;
233 rv
= sc
->GetBuffer(spec
.get(), &buf
, &len
);
234 if (NS_FAILED(rv
)) return NS_ERROR_NOT_AVAILABLE
;
236 rv
= NewObjectInputStreamFromBuffer(buf
, len
, getter_AddRefs(ois
));
237 NS_ENSURE_SUCCESS(rv
, rv
);
239 mInputStreamTable
.InsertOrUpdate(uri
, ois
);
245 nsresult
nsXULPrototypeCache::FinishInputStream(nsIURI
* uri
) {
246 mInputStreamTable
.Remove(uri
);
250 nsresult
nsXULPrototypeCache::GetOutputStream(nsIURI
* uri
,
251 nsIObjectOutputStream
** stream
) {
253 nsCOMPtr
<nsIObjectOutputStream
> objectOutput
;
254 nsCOMPtr
<nsIStorageStream
> storageStream
;
255 bool found
= mOutputStreamTable
.Get(uri
, getter_AddRefs(storageStream
));
257 // Setting an output stream here causes crashes on Windows. The previous
258 // version of this code always returned NS_ERROR_OUT_OF_MEMORY here,
259 // because it used a mistyped contract ID to create its object stream.
260 return NS_ERROR_NOT_IMPLEMENTED
;
262 nsCOMPtr
<nsIOutputStream
> outputStream
263 = do_QueryInterface(storageStream
);
264 objectOutput
= NS_NewObjectOutputStream(outputStream
);
267 rv
= NewObjectOutputWrappedStorageStream(
268 getter_AddRefs(objectOutput
), getter_AddRefs(storageStream
), false);
269 NS_ENSURE_SUCCESS(rv
, rv
);
270 mOutputStreamTable
.InsertOrUpdate(uri
, storageStream
);
272 objectOutput
.forget(stream
);
276 nsresult
nsXULPrototypeCache::FinishOutputStream(CacheType cacheType
,
279 StartupCache
* sc
= StartupCache::GetSingleton();
280 if (!sc
) return NS_ERROR_NOT_AVAILABLE
;
282 nsCOMPtr
<nsIStorageStream
> storageStream
;
283 bool found
= mOutputStreamTable
.Get(uri
, getter_AddRefs(storageStream
));
284 if (!found
) return NS_ERROR_UNEXPECTED
;
285 nsCOMPtr
<nsIOutputStream
> outputStream
= do_QueryInterface(storageStream
);
286 outputStream
->Close();
288 UniquePtr
<char[]> buf
;
290 rv
= NewBufferFromStorageStream(storageStream
, &buf
, &len
);
291 NS_ENSURE_SUCCESS(rv
, rv
);
293 if (!mStartupCacheURITable
.GetEntry(uri
)) {
295 rv
= PathifyURIForType(cacheType
, uri
, spec
);
296 if (NS_FAILED(rv
)) return NS_ERROR_NOT_AVAILABLE
;
297 rv
= sc
->PutBuffer(spec
.get(), std::move(buf
), len
);
298 if (NS_SUCCEEDED(rv
)) {
299 mOutputStreamTable
.Remove(uri
);
300 mStartupCacheURITable
.PutEntry(uri
);
307 // We have data if we're in the middle of writing it or we already
308 // have it in the cache.
309 nsresult
nsXULPrototypeCache::HasData(CacheType cacheType
, nsIURI
* uri
,
311 if (mOutputStreamTable
.Get(uri
, nullptr)) {
316 nsresult rv
= PathifyURIForType(cacheType
, uri
, spec
);
321 UniquePtr
<char[]> buf
;
322 StartupCache
* sc
= StartupCache::GetSingleton();
324 *exists
= sc
->HasEntry(spec
.get());
331 nsresult
nsXULPrototypeCache::BeginCaching(nsIURI
* aURI
) {
335 aURI
->GetPathQueryRef(path
);
336 if (!(StringEndsWith(path
, ".xul"_ns
) || StringEndsWith(path
, ".xhtml"_ns
))) {
337 return NS_ERROR_NOT_AVAILABLE
;
340 StartupCache
* startupCache
= StartupCache::GetSingleton();
341 if (!startupCache
) return NS_ERROR_FAILURE
;
343 if (StaticPrefs::nglayout_debug_disable_xul_cache()) {
344 return NS_ERROR_NOT_AVAILABLE
;
347 // Get the chrome directory to validate against the one stored in the
348 // cache file, or to store there if we're generating a new file.
349 nsCOMPtr
<nsIFile
> chromeDir
;
350 rv
= NS_GetSpecialDirectory(NS_APP_CHROME_DIR
, getter_AddRefs(chromeDir
));
351 if (NS_FAILED(rv
)) return rv
;
352 nsAutoCString chromePath
;
353 rv
= chromeDir
->GetPersistentDescriptor(chromePath
);
354 if (NS_FAILED(rv
)) return rv
;
356 // XXXbe we assume the first package's locale is the same as the locale of
357 // all subsequent packages of cached chrome URIs....
358 nsAutoCString package
;
359 rv
= aURI
->GetHost(package
);
360 if (NS_FAILED(rv
)) return rv
;
361 nsAutoCString locale
;
362 LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale
);
364 nsAutoCString fileChromePath
, fileLocale
;
366 const char* buf
= nullptr;
367 uint32_t len
, amtRead
;
368 nsCOMPtr
<nsIObjectInputStream
> objectInput
;
370 rv
= startupCache
->GetBuffer(kXULCacheInfoKey
, &buf
, &len
);
371 if (NS_SUCCEEDED(rv
))
372 rv
= NewObjectInputStreamFromBuffer(buf
, len
, getter_AddRefs(objectInput
));
374 if (NS_SUCCEEDED(rv
)) {
375 rv
= objectInput
->ReadCString(fileLocale
);
376 tmp
= objectInput
->ReadCString(fileChromePath
);
377 if (NS_FAILED(tmp
)) {
381 (!fileChromePath
.Equals(chromePath
) || !fileLocale
.Equals(locale
))) {
382 // Our cache won't be valid in this case, we'll need to rewrite.
383 // XXX This blows away work that other consumers (like
384 // mozJSComponentLoader) have done, need more fine-grained control.
385 startupCache
->InvalidateCache();
386 mStartupCacheURITable
.Clear();
387 rv
= NS_ERROR_UNEXPECTED
;
389 } else if (rv
!= NS_ERROR_NOT_AVAILABLE
)
390 // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
394 // Either the cache entry was invalid or it didn't exist, so write it now.
395 nsCOMPtr
<nsIObjectOutputStream
> objectOutput
;
396 nsCOMPtr
<nsIInputStream
> inputStream
;
397 nsCOMPtr
<nsIStorageStream
> storageStream
;
398 rv
= NewObjectOutputWrappedStorageStream(
399 getter_AddRefs(objectOutput
), getter_AddRefs(storageStream
), false);
400 if (NS_SUCCEEDED(rv
)) {
401 rv
= objectOutput
->WriteStringZ(locale
.get());
402 tmp
= objectOutput
->WriteStringZ(chromePath
.get());
403 if (NS_FAILED(tmp
)) {
406 tmp
= objectOutput
->Close();
407 if (NS_FAILED(tmp
)) {
410 tmp
= storageStream
->NewInputStream(0, getter_AddRefs(inputStream
));
411 if (NS_FAILED(tmp
)) {
416 if (NS_SUCCEEDED(rv
)) {
418 rv
= inputStream
->Available(&len64
);
419 if (NS_SUCCEEDED(rv
)) {
420 if (len64
<= UINT32_MAX
)
421 len
= (uint32_t)len64
;
423 rv
= NS_ERROR_FILE_TOO_BIG
;
427 if (NS_SUCCEEDED(rv
)) {
428 auto putBuf
= MakeUnique
<char[]>(len
);
429 rv
= inputStream
->Read(putBuf
.get(), len
, &amtRead
);
430 if (NS_SUCCEEDED(rv
) && len
== amtRead
)
431 rv
= startupCache
->PutBuffer(kXULCacheInfoKey
, std::move(putBuf
), len
);
433 rv
= NS_ERROR_UNEXPECTED
;
437 // Failed again, just bail.
439 startupCache
->InvalidateCache();
440 mStartupCacheURITable
.Clear();
441 return NS_ERROR_FAILURE
;
448 void nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration
) {
449 for (const auto& prototype
: mPrototypeTable
.Values()) {
450 prototype
->MarkInCCGeneration(aGeneration
);
454 MOZ_DEFINE_MALLOC_SIZE_OF(CacheMallocSizeOf
)
456 static void ReportSize(const nsCString
& aPath
, size_t aAmount
,
457 const nsCString
& aDescription
,
458 nsIHandleReportCallback
* aHandleReport
,
459 nsISupports
* aData
) {
460 nsAutoCString
path("explicit/xul-prototype-cache/");
462 aHandleReport
->Callback(""_ns
, path
, nsIMemoryReporter::KIND_HEAP
,
463 nsIMemoryReporter::UNITS_BYTES
, aAmount
, aDescription
,
468 void nsXULPrototypeCache::CollectMemoryReports(
469 nsIHandleReportCallback
* aHandleReport
, nsISupports
* aData
) {
474 MallocSizeOf mallocSizeOf
= CacheMallocSizeOf
;
475 size_t other
= mallocSizeOf(sInstance
);
477 #define REPORT_SIZE(_path, _amount, _desc) \
478 ReportSize(_path, _amount, nsLiteralCString(_desc), aHandleReport, aData)
480 other
+= sInstance
->mPrototypeTable
.ShallowSizeOfExcludingThis(mallocSizeOf
);
481 // TODO Report content in mPrototypeTable?
483 other
+= sInstance
->mStencilTable
.ShallowSizeOfExcludingThis(mallocSizeOf
);
484 // TODO Report content inside mStencilTable?
487 sInstance
->mStartupCacheURITable
.ShallowSizeOfExcludingThis(mallocSizeOf
);
490 sInstance
->mOutputStreamTable
.ShallowSizeOfExcludingThis(mallocSizeOf
);
492 sInstance
->mInputStreamTable
.ShallowSizeOfExcludingThis(mallocSizeOf
);
494 REPORT_SIZE("other"_ns
, other
,
496 "the instance and tables of the XUL prototype cache.");