Bug 1769952 - Fix running raptor on a Win10-64 VM r=sparky
[gecko.git] / dom / xul / nsXULPrototypeCache.cpp
blobbf37bcad447b65b57c2203c9469d099fcb473a14
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"
9 #include "plstr.h"
10 #include "nsXULPrototypeDocument.h"
11 #include "nsIURI.h"
12 #include "nsNetUtil.h"
14 #include "nsIFile.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)
59 /* static */
60 nsXULPrototypeCache* nsXULPrototypeCache::GetInstance() {
61 if (!sInstance) {
62 NS_ADDREF(sInstance = new nsXULPrototypeCache());
64 Preferences::RegisterCallback(
65 DisableXULCacheChangedCallback,
66 nsDependentCString(
67 StaticPrefs::GetPrefName_nglayout_debug_disable_xul_cache()));
69 nsCOMPtr<nsIObserverService> obsSvc =
70 mozilla::services::GetObserverService();
71 if (obsSvc) {
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);
78 return sInstance;
81 //----------------------------------------------------------------------
83 NS_IMETHODIMP
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)) {
88 Flush();
89 } else if (!strcmp(aTopic, "startupcache-invalidate")) {
90 AbortCaching();
91 } else {
92 NS_WARNING("Unexpected observer topic.");
94 return NS_OK;
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);
104 if (protoDoc) {
105 return protoDoc;
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));
114 if (NS_FAILED(rv)) {
115 return nullptr;
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);
125 } else {
126 newProto = nullptr;
129 mInputStreamTable.Remove(aURI);
130 return newProto;
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});
144 return NS_OK;
147 JS::Stencil* nsXULPrototypeCache::GetStencil(nsIURI* aURI) {
148 if (auto* entry = mStencilTable.GetEntry(aURI)) {
149 return entry->mStencil;
151 return nullptr;
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());
166 #endif
168 mStencilTable.PutEntry(aURI)->mStencil = aStencil;
170 return NS_OK;
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
184 // script, somehow.
185 Flush();
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) {
211 switch (cacheType) {
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) {
223 nsAutoCString spec;
224 nsresult rv = PathifyURIForType(cacheType, uri, spec);
225 if (NS_FAILED(rv)) return NS_ERROR_NOT_AVAILABLE;
227 const char* buf;
228 uint32_t len;
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);
241 ois.forget(stream);
242 return NS_OK;
245 nsresult nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
246 mInputStreamTable.Remove(uri);
247 return NS_OK;
250 nsresult nsXULPrototypeCache::GetOutputStream(nsIURI* uri,
251 nsIObjectOutputStream** stream) {
252 nsresult rv;
253 nsCOMPtr<nsIObjectOutputStream> objectOutput;
254 nsCOMPtr<nsIStorageStream> storageStream;
255 bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
256 if (found) {
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;
261 #if 0
262 nsCOMPtr<nsIOutputStream> outputStream
263 = do_QueryInterface(storageStream);
264 objectOutput = NS_NewObjectOutputStream(outputStream);
265 #endif
266 } else {
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);
273 return NS_OK;
276 nsresult nsXULPrototypeCache::FinishOutputStream(CacheType cacheType,
277 nsIURI* uri) {
278 nsresult rv;
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;
289 uint32_t len;
290 rv = NewBufferFromStorageStream(storageStream, &buf, &len);
291 NS_ENSURE_SUCCESS(rv, rv);
293 if (!mStartupCacheURITable.GetEntry(uri)) {
294 nsAutoCString spec;
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);
304 return rv;
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,
310 bool* exists) {
311 if (mOutputStreamTable.Get(uri, nullptr)) {
312 *exists = true;
313 return NS_OK;
315 nsAutoCString spec;
316 nsresult rv = PathifyURIForType(cacheType, uri, spec);
317 if (NS_FAILED(rv)) {
318 *exists = false;
319 return NS_OK;
321 UniquePtr<char[]> buf;
322 StartupCache* sc = StartupCache::GetSingleton();
323 if (sc) {
324 *exists = sc->HasEntry(spec.get());
325 } else {
326 *exists = false;
328 return NS_OK;
331 nsresult nsXULPrototypeCache::BeginCaching(nsIURI* aURI) {
332 nsresult rv, tmp;
334 nsAutoCString path;
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)) {
378 rv = tmp;
380 if (NS_FAILED(rv) ||
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.
391 return rv;
393 if (NS_FAILED(rv)) {
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)) {
404 rv = tmp;
406 tmp = objectOutput->Close();
407 if (NS_FAILED(tmp)) {
408 rv = tmp;
410 tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
411 if (NS_FAILED(tmp)) {
412 rv = tmp;
416 if (NS_SUCCEEDED(rv)) {
417 uint64_t len64;
418 rv = inputStream->Available(&len64);
419 if (NS_SUCCEEDED(rv)) {
420 if (len64 <= UINT32_MAX)
421 len = (uint32_t)len64;
422 else
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);
432 else {
433 rv = NS_ERROR_UNEXPECTED;
437 // Failed again, just bail.
438 if (NS_FAILED(rv)) {
439 startupCache->InvalidateCache();
440 mStartupCacheURITable.Clear();
441 return NS_ERROR_FAILURE;
445 return NS_OK;
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/");
461 path += aPath;
462 aHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
463 nsIMemoryReporter::UNITS_BYTES, aAmount, aDescription,
464 aData);
467 /* static */
468 void nsXULPrototypeCache::CollectMemoryReports(
469 nsIHandleReportCallback* aHandleReport, nsISupports* aData) {
470 if (!sInstance) {
471 return;
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?
486 other +=
487 sInstance->mStartupCacheURITable.ShallowSizeOfExcludingThis(mallocSizeOf);
489 other +=
490 sInstance->mOutputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
491 other +=
492 sInstance->mInputStreamTable.ShallowSizeOfExcludingThis(mallocSizeOf);
494 REPORT_SIZE("other"_ns, other,
495 "Memory used by "
496 "the instance and tables of the XUL prototype cache.");
498 #undef REPORT_SIZE