1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsXULPrototypeCache.h"
9 #include "nsXULPrototypeDocument.h"
10 #include "nsIServiceManager.h"
13 #include "nsIChromeRegistry.h"
15 #include "nsIObjectInputStream.h"
16 #include "nsIObjectOutputStream.h"
17 #include "nsIObserverService.h"
18 #include "nsIStringStream.h"
19 #include "nsIStorageStream.h"
21 #include "nsNetUtil.h"
22 #include "nsAppDirectoryServiceDefs.h"
24 #include "js/TracingAPI.h"
26 #include "mozilla/CSSStyleSheet.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/scache/StartupCache.h"
29 #include "mozilla/scache/StartupCacheUtils.h"
30 #include "mozilla/Telemetry.h"
32 using namespace mozilla
;
33 using namespace mozilla::scache
;
35 static bool gDisableXULCache
= false; // enabled by default
36 static const char kDisableXULCachePref
[] = "nglayout.debug.disable_xul_cache";
37 static const char kXULCacheInfoKey
[] = "nsXULPrototypeCache.startupCache";
38 static const char kXULCachePrefix
[] = "xulcache";
40 //----------------------------------------------------------------------
43 UpdategDisableXULCache()
45 // Get the value of "nglayout.debug.disable_xul_cache" preference
47 Preferences::GetBool(kDisableXULCachePref
, gDisableXULCache
);
49 // Sets the flag if the XUL cache is disabled
50 if (gDisableXULCache
) {
51 Telemetry::Accumulate(Telemetry::XUL_CACHE_DISABLED
, true);
57 DisableXULCacheChangedCallback(const char* aPref
, void* aClosure
)
59 UpdategDisableXULCache();
61 // Flush the cache, regardless
62 nsXULPrototypeCache
* cache
= nsXULPrototypeCache::GetInstance();
67 //----------------------------------------------------------------------
69 nsXULPrototypeCache
* nsXULPrototypeCache::sInstance
= nullptr;
72 nsXULPrototypeCache::nsXULPrototypeCache()
77 nsXULPrototypeCache::~nsXULPrototypeCache()
83 NS_IMPL_ISUPPORTS(nsXULPrototypeCache
, nsIObserver
)
85 /* static */ nsXULPrototypeCache
*
86 nsXULPrototypeCache::GetInstance()
89 NS_ADDREF(sInstance
= new nsXULPrototypeCache());
91 UpdategDisableXULCache();
93 Preferences::RegisterCallback(DisableXULCacheChangedCallback
,
94 kDisableXULCachePref
);
96 nsCOMPtr
<nsIObserverService
> obsSvc
=
97 mozilla::services::GetObserverService();
99 nsXULPrototypeCache
*p
= sInstance
;
100 obsSvc
->AddObserver(p
, "chrome-flush-skin-caches", false);
101 obsSvc
->AddObserver(p
, "chrome-flush-caches", false);
102 obsSvc
->AddObserver(p
, "startupcache-invalidate", false);
109 //----------------------------------------------------------------------
112 nsXULPrototypeCache::Observe(nsISupports
* aSubject
,
114 const char16_t
*aData
)
116 if (!strcmp(aTopic
, "chrome-flush-skin-caches")) {
119 else if (!strcmp(aTopic
, "chrome-flush-caches")) {
122 else if (!strcmp(aTopic
, "startupcache-invalidate")) {
126 NS_WARNING("Unexpected observer topic.");
131 nsXULPrototypeDocument
*
132 nsXULPrototypeCache::GetPrototype(nsIURI
* aURI
)
137 nsCOMPtr
<nsIURI
> uriWithoutRef
;
138 aURI
->CloneIgnoringRef(getter_AddRefs(uriWithoutRef
));
140 nsXULPrototypeDocument
* protoDoc
= mPrototypeTable
.GetWeak(uriWithoutRef
);
144 nsresult rv
= BeginCaching(aURI
);
148 // No prototype in XUL memory cache. Spin up the cache Service.
149 nsCOMPtr
<nsIObjectInputStream
> ois
;
150 rv
= GetInputStream(aURI
, getter_AddRefs(ois
));
154 nsRefPtr
<nsXULPrototypeDocument
> newProto
;
155 rv
= NS_NewXULPrototypeDocument(getter_AddRefs(newProto
));
159 rv
= newProto
->Read(ois
);
160 if (NS_SUCCEEDED(rv
)) {
161 rv
= PutPrototype(newProto
);
166 mInputStreamTable
.Remove(aURI
);
171 nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument
* aDocument
)
173 if (!aDocument
->GetURI()) {
174 return NS_ERROR_FAILURE
;
177 nsCOMPtr
<nsIURI
> uri
;
178 aDocument
->GetURI()->CloneIgnoringRef(getter_AddRefs(uri
));
180 // Put() releases any old value and addrefs the new one
181 mPrototypeTable
.Put(uri
, aDocument
);
187 nsXULPrototypeCache::PutStyleSheet(CSSStyleSheet
* aStyleSheet
)
189 nsIURI
* uri
= aStyleSheet
->GetSheetURI();
191 mStyleSheetTable
.Put(uri
, aStyleSheet
);
198 nsXULPrototypeCache::GetScript(nsIURI
* aURI
)
200 return mScriptTable
.Get(aURI
);
204 nsXULPrototypeCache::PutScript(nsIURI
* aURI
,
205 JS::Handle
<JSScript
*> aScriptObject
)
207 MOZ_ASSERT(aScriptObject
, "Need a non-NULL script");
210 if (mScriptTable
.Get(aURI
)) {
211 nsAutoCString scriptName
;
212 aURI
->GetSpec(scriptName
);
213 nsAutoCString
message("Loaded script ");
214 message
+= scriptName
;
215 message
+= " twice (bug 392650)";
216 NS_WARNING(message
.get());
220 mScriptTable
.Put(aURI
, aScriptObject
);
226 nsXULPrototypeCache::PutXBLDocumentInfo(nsXBLDocumentInfo
* aDocumentInfo
)
228 nsIURI
* uri
= aDocumentInfo
->DocumentURI();
230 nsRefPtr
<nsXBLDocumentInfo
> info
;
231 mXBLDocTable
.Get(uri
, getter_AddRefs(info
));
233 mXBLDocTable
.Put(uri
, aDocumentInfo
);
238 static PLDHashOperator
239 FlushSkinXBL(nsIURI
* aKey
, nsRefPtr
<nsXBLDocumentInfo
>& aDocInfo
, void* aClosure
)
244 PLDHashOperator ret
= PL_DHASH_NEXT
;
246 if (!strncmp(str
.get(), "/skin", 5)) {
247 ret
= PL_DHASH_REMOVE
;
253 static PLDHashOperator
254 FlushSkinSheets(nsIURI
* aKey
, nsRefPtr
<CSSStyleSheet
>& aSheet
, void* aClosure
)
257 aSheet
->GetSheetURI()->GetPath(str
);
259 PLDHashOperator ret
= PL_DHASH_NEXT
;
261 if (!strncmp(str
.get(), "/skin", 5)) {
262 // This is a skin binding. Add the key to the list.
263 ret
= PL_DHASH_REMOVE
;
268 static PLDHashOperator
269 FlushScopedSkinStylesheets(nsIURI
* aKey
, nsRefPtr
<nsXBLDocumentInfo
> &aDocInfo
, void* aClosure
)
271 aDocInfo
->FlushSkinStylesheets();
272 return PL_DHASH_NEXT
;
276 nsXULPrototypeCache::FlushSkinFiles()
278 // Flush out skin XBL files from the cache.
279 mXBLDocTable
.Enumerate(FlushSkinXBL
, nullptr);
281 // Now flush out our skin stylesheets from the cache.
282 mStyleSheetTable
.Enumerate(FlushSkinSheets
, nullptr);
284 // Iterate over all the remaining XBL and make sure cached
285 // scoped skin stylesheets are flushed and refetched by the
286 // prototype bindings.
287 mXBLDocTable
.Enumerate(FlushScopedSkinStylesheets
, nullptr);
291 nsXULPrototypeCache::FlushScripts()
293 mScriptTable
.Clear();
297 nsXULPrototypeCache::Flush()
299 mPrototypeTable
.Clear();
300 mScriptTable
.Clear();
301 mStyleSheetTable
.Clear();
302 mXBLDocTable
.Clear();
307 nsXULPrototypeCache::IsEnabled()
309 return !gDisableXULCache
;
312 static bool gDisableXULDiskCache
= false; // enabled by default
315 nsXULPrototypeCache::AbortCaching()
321 // Flush the XUL cache for good measure, in case we cached a bogus/downrev
325 // Clear the cache set
326 mCacheURITable
.Clear();
331 nsXULPrototypeCache::WritePrototype(nsXULPrototypeDocument
* aPrototypeDocument
)
333 nsresult rv
= NS_OK
, rv2
= NS_OK
;
335 if (!StartupCache::GetSingleton())
338 nsCOMPtr
<nsIURI
> protoURI
= aPrototypeDocument
->GetURI();
340 nsCOMPtr
<nsIObjectOutputStream
> oos
;
341 rv
= GetOutputStream(protoURI
, getter_AddRefs(oos
));
342 NS_ENSURE_SUCCESS(rv
, rv
);
344 rv
= aPrototypeDocument
->Write(oos
);
345 NS_ENSURE_SUCCESS(rv
, rv
);
346 FinishOutputStream(protoURI
);
347 return NS_FAILED(rv
) ? rv
: rv2
;
351 nsXULPrototypeCache::GetInputStream(nsIURI
* uri
, nsIObjectInputStream
** stream
)
353 nsAutoCString
spec(kXULCachePrefix
);
354 nsresult rv
= PathifyURI(uri
, spec
);
356 return NS_ERROR_NOT_AVAILABLE
;
358 nsAutoArrayPtr
<char> buf
;
360 nsCOMPtr
<nsIObjectInputStream
> ois
;
361 StartupCache
* sc
= StartupCache::GetSingleton();
363 return NS_ERROR_NOT_AVAILABLE
;
365 rv
= sc
->GetBuffer(spec
.get(), getter_Transfers(buf
), &len
);
367 return NS_ERROR_NOT_AVAILABLE
;
369 rv
= NewObjectInputStreamFromBuffer(buf
, len
, getter_AddRefs(ois
));
370 NS_ENSURE_SUCCESS(rv
, rv
);
373 mInputStreamTable
.Put(uri
, ois
);
375 NS_ADDREF(*stream
= ois
);
380 nsXULPrototypeCache::FinishInputStream(nsIURI
* uri
) {
381 mInputStreamTable
.Remove(uri
);
386 nsXULPrototypeCache::GetOutputStream(nsIURI
* uri
, nsIObjectOutputStream
** stream
)
389 nsCOMPtr
<nsIObjectOutputStream
> objectOutput
;
390 nsCOMPtr
<nsIStorageStream
> storageStream
;
391 bool found
= mOutputStreamTable
.Get(uri
, getter_AddRefs(storageStream
));
393 objectOutput
= do_CreateInstance("mozilla.org/binaryoutputstream;1");
394 if (!objectOutput
) return NS_ERROR_OUT_OF_MEMORY
;
395 nsCOMPtr
<nsIOutputStream
> outputStream
396 = do_QueryInterface(storageStream
);
397 objectOutput
->SetOutputStream(outputStream
);
399 rv
= NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput
),
400 getter_AddRefs(storageStream
),
402 NS_ENSURE_SUCCESS(rv
, rv
);
403 mOutputStreamTable
.Put(uri
, storageStream
);
405 NS_ADDREF(*stream
= objectOutput
);
410 nsXULPrototypeCache::FinishOutputStream(nsIURI
* uri
)
413 StartupCache
* sc
= StartupCache::GetSingleton();
415 return NS_ERROR_NOT_AVAILABLE
;
417 nsCOMPtr
<nsIStorageStream
> storageStream
;
418 bool found
= mOutputStreamTable
.Get(uri
, getter_AddRefs(storageStream
));
420 return NS_ERROR_UNEXPECTED
;
421 nsCOMPtr
<nsIOutputStream
> outputStream
422 = do_QueryInterface(storageStream
);
423 outputStream
->Close();
425 nsAutoArrayPtr
<char> buf
;
427 rv
= NewBufferFromStorageStream(storageStream
, getter_Transfers(buf
),
429 NS_ENSURE_SUCCESS(rv
, rv
);
431 if (!mCacheURITable
.GetEntry(uri
)) {
432 nsAutoCString
spec(kXULCachePrefix
);
433 rv
= PathifyURI(uri
, spec
);
435 return NS_ERROR_NOT_AVAILABLE
;
436 rv
= sc
->PutBuffer(spec
.get(), buf
, len
);
437 if (NS_SUCCEEDED(rv
)) {
438 mOutputStreamTable
.Remove(uri
);
439 mCacheURITable
.RemoveEntry(uri
);
446 // We have data if we're in the middle of writing it or we already
447 // have it in the cache.
449 nsXULPrototypeCache::HasData(nsIURI
* uri
, bool* exists
)
451 if (mOutputStreamTable
.Get(uri
, nullptr)) {
455 nsAutoCString
spec(kXULCachePrefix
);
456 nsresult rv
= PathifyURI(uri
, spec
);
461 nsAutoArrayPtr
<char> buf
;
463 StartupCache
* sc
= StartupCache::GetSingleton();
465 rv
= sc
->GetBuffer(spec
.get(), getter_Transfers(buf
), &len
);
470 *exists
= NS_SUCCEEDED(rv
);
475 CachePrefChangedCallback(const char* aPref
, void* aClosure
)
477 bool wasEnabled
= !gDisableXULDiskCache
;
478 gDisableXULDiskCache
=
479 Preferences::GetBool(kDisableXULCachePref
,
480 gDisableXULDiskCache
);
482 if (wasEnabled
&& gDisableXULDiskCache
) {
483 nsXULPrototypeCache
* cache
= nsXULPrototypeCache::GetInstance();
486 cache
->AbortCaching();
491 nsXULPrototypeCache::BeginCaching(nsIURI
* aURI
)
497 if (!StringEndsWith(path
, NS_LITERAL_CSTRING(".xul")))
498 return NS_ERROR_NOT_AVAILABLE
;
500 StartupCache
* startupCache
= StartupCache::GetSingleton();
502 return NS_ERROR_FAILURE
;
504 gDisableXULDiskCache
=
505 Preferences::GetBool(kDisableXULCachePref
, gDisableXULDiskCache
);
507 Preferences::RegisterCallback(CachePrefChangedCallback
,
508 kDisableXULCachePref
);
510 if (gDisableXULDiskCache
)
511 return NS_ERROR_NOT_AVAILABLE
;
513 // Get the chrome directory to validate against the one stored in the
514 // cache file, or to store there if we're generating a new file.
515 nsCOMPtr
<nsIFile
> chromeDir
;
516 rv
= NS_GetSpecialDirectory(NS_APP_CHROME_DIR
, getter_AddRefs(chromeDir
));
519 nsAutoCString chromePath
;
520 rv
= chromeDir
->GetNativePath(chromePath
);
524 // XXXbe we assume the first package's locale is the same as the locale of
525 // all subsequent packages of cached chrome URIs....
526 nsAutoCString package
;
527 rv
= aURI
->GetHost(package
);
530 nsCOMPtr
<nsIXULChromeRegistry
> chromeReg
531 = do_GetService(NS_CHROMEREGISTRY_CONTRACTID
, &rv
);
532 nsAutoCString locale
;
533 rv
= chromeReg
->GetSelectedLocale(package
, locale
);
537 nsAutoCString fileChromePath
, fileLocale
;
539 nsAutoArrayPtr
<char> buf
;
540 uint32_t len
, amtRead
;
541 nsCOMPtr
<nsIObjectInputStream
> objectInput
;
543 rv
= startupCache
->GetBuffer(kXULCacheInfoKey
, getter_Transfers(buf
),
545 if (NS_SUCCEEDED(rv
))
546 rv
= NewObjectInputStreamFromBuffer(buf
, len
, getter_AddRefs(objectInput
));
548 if (NS_SUCCEEDED(rv
)) {
550 rv
= objectInput
->ReadCString(fileLocale
);
551 tmp
= objectInput
->ReadCString(fileChromePath
);
552 if (NS_FAILED(tmp
)) {
556 (!fileChromePath
.Equals(chromePath
) ||
557 !fileLocale
.Equals(locale
))) {
558 // Our cache won't be valid in this case, we'll need to rewrite.
559 // XXX This blows away work that other consumers (like
560 // mozJSComponentLoader) have done, need more fine-grained control.
561 startupCache
->InvalidateCache();
562 rv
= NS_ERROR_UNEXPECTED
;
564 } else if (rv
!= NS_ERROR_NOT_AVAILABLE
)
565 // NS_ERROR_NOT_AVAILABLE is normal, usually if there's no cachefile.
569 // Either the cache entry was invalid or it didn't exist, so write it now.
570 nsCOMPtr
<nsIObjectOutputStream
> objectOutput
;
571 nsCOMPtr
<nsIInputStream
> inputStream
;
572 nsCOMPtr
<nsIStorageStream
> storageStream
;
573 rv
= NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput
),
574 getter_AddRefs(storageStream
),
576 if (NS_SUCCEEDED(rv
)) {
577 rv
= objectOutput
->WriteStringZ(locale
.get());
578 tmp
= objectOutput
->WriteStringZ(chromePath
.get());
579 if (NS_FAILED(tmp
)) {
582 tmp
= objectOutput
->Close();
583 if (NS_FAILED(tmp
)) {
586 tmp
= storageStream
->NewInputStream(0, getter_AddRefs(inputStream
));
587 if (NS_FAILED(tmp
)) {
592 if (NS_SUCCEEDED(rv
)) {
594 rv
= inputStream
->Available(&len64
);
595 if (NS_SUCCEEDED(rv
)) {
596 if (len64
<= UINT32_MAX
)
597 len
= (uint32_t)len64
;
599 rv
= NS_ERROR_FILE_TOO_BIG
;
603 if (NS_SUCCEEDED(rv
)) {
605 rv
= inputStream
->Read(buf
, len
, &amtRead
);
606 if (NS_SUCCEEDED(rv
) && len
== amtRead
)
607 rv
= startupCache
->PutBuffer(kXULCacheInfoKey
, buf
, len
);
609 rv
= NS_ERROR_UNEXPECTED
;
613 // Failed again, just bail.
615 startupCache
->InvalidateCache();
616 return NS_ERROR_FAILURE
;
620 // Success! Insert this URI into the mCacheURITable
621 // and commit locals to globals.
622 mCacheURITable
.PutEntry(aURI
);
627 static PLDHashOperator
628 MarkXBLInCCGeneration(nsIURI
* aKey
, nsRefPtr
<nsXBLDocumentInfo
> &aDocInfo
,
631 uint32_t* gen
= static_cast<uint32_t*>(aClosure
);
632 aDocInfo
->MarkInCCGeneration(*gen
);
633 return PL_DHASH_NEXT
;
636 static PLDHashOperator
637 MarkXULInCCGeneration(nsIURI
* aKey
, nsRefPtr
<nsXULPrototypeDocument
> &aDoc
,
640 uint32_t* gen
= static_cast<uint32_t*>(aClosure
);
641 aDoc
->MarkInCCGeneration(*gen
);
642 return PL_DHASH_NEXT
;
646 nsXULPrototypeCache::MarkInCCGeneration(uint32_t aGeneration
)
648 mXBLDocTable
.Enumerate(MarkXBLInCCGeneration
, &aGeneration
);
649 mPrototypeTable
.Enumerate(MarkXULInCCGeneration
, &aGeneration
);
652 static PLDHashOperator
653 MarkScriptsInGC(nsIURI
* aKey
, JS::Heap
<JSScript
*>& aScript
, void* aClosure
)
655 JSTracer
* trc
= static_cast<JSTracer
*>(aClosure
);
656 JS_CallScriptTracer(trc
, &aScript
, "nsXULPrototypeCache script");
657 return PL_DHASH_NEXT
;
661 nsXULPrototypeCache::MarkInGC(JSTracer
* aTrc
)
663 mScriptTable
.Enumerate(MarkScriptsInGC
, aTrc
);