Bumping manifests a=b2g-bump
[gecko.git] / dom / xul / nsXULPrototypeCache.cpp
blobea0558c456e006cb49aeaf6933adc8cb6b2d348a
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"
8 #include "plstr.h"
9 #include "nsXULPrototypeDocument.h"
10 #include "nsIServiceManager.h"
11 #include "nsIURI.h"
13 #include "nsIChromeRegistry.h"
14 #include "nsIFile.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 //----------------------------------------------------------------------
42 static void
43 UpdategDisableXULCache()
45 // Get the value of "nglayout.debug.disable_xul_cache" preference
46 gDisableXULCache =
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);
56 static void
57 DisableXULCacheChangedCallback(const char* aPref, void* aClosure)
59 UpdategDisableXULCache();
61 // Flush the cache, regardless
62 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
63 if (cache)
64 cache->Flush();
67 //----------------------------------------------------------------------
69 nsXULPrototypeCache* nsXULPrototypeCache::sInstance = nullptr;
72 nsXULPrototypeCache::nsXULPrototypeCache()
77 nsXULPrototypeCache::~nsXULPrototypeCache()
79 FlushScripts();
83 NS_IMPL_ISUPPORTS(nsXULPrototypeCache, nsIObserver)
85 /* static */ nsXULPrototypeCache*
86 nsXULPrototypeCache::GetInstance()
88 if (!sInstance) {
89 NS_ADDREF(sInstance = new nsXULPrototypeCache());
91 UpdategDisableXULCache();
93 Preferences::RegisterCallback(DisableXULCacheChangedCallback,
94 kDisableXULCachePref);
96 nsCOMPtr<nsIObserverService> obsSvc =
97 mozilla::services::GetObserverService();
98 if (obsSvc) {
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);
106 return sInstance;
109 //----------------------------------------------------------------------
111 NS_IMETHODIMP
112 nsXULPrototypeCache::Observe(nsISupports* aSubject,
113 const char *aTopic,
114 const char16_t *aData)
116 if (!strcmp(aTopic, "chrome-flush-skin-caches")) {
117 FlushSkinFiles();
119 else if (!strcmp(aTopic, "chrome-flush-caches")) {
120 Flush();
122 else if (!strcmp(aTopic, "startupcache-invalidate")) {
123 AbortCaching();
125 else {
126 NS_WARNING("Unexpected observer topic.");
128 return NS_OK;
131 nsXULPrototypeDocument*
132 nsXULPrototypeCache::GetPrototype(nsIURI* aURI)
134 if (!aURI)
135 return nullptr;
137 nsCOMPtr<nsIURI> uriWithoutRef;
138 aURI->CloneIgnoringRef(getter_AddRefs(uriWithoutRef));
140 nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef);
141 if (protoDoc)
142 return protoDoc;
144 nsresult rv = BeginCaching(aURI);
145 if (NS_FAILED(rv))
146 return nullptr;
148 // No prototype in XUL memory cache. Spin up the cache Service.
149 nsCOMPtr<nsIObjectInputStream> ois;
150 rv = GetInputStream(aURI, getter_AddRefs(ois));
151 if (NS_FAILED(rv))
152 return nullptr;
154 nsRefPtr<nsXULPrototypeDocument> newProto;
155 rv = NS_NewXULPrototypeDocument(getter_AddRefs(newProto));
156 if (NS_FAILED(rv))
157 return nullptr;
159 rv = newProto->Read(ois);
160 if (NS_SUCCEEDED(rv)) {
161 rv = PutPrototype(newProto);
162 } else {
163 newProto = nullptr;
166 mInputStreamTable.Remove(aURI);
167 return newProto;
170 nsresult
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);
183 return NS_OK;
186 nsresult
187 nsXULPrototypeCache::PutStyleSheet(CSSStyleSheet* aStyleSheet)
189 nsIURI* uri = aStyleSheet->GetSheetURI();
191 mStyleSheetTable.Put(uri, aStyleSheet);
193 return NS_OK;
197 JSScript*
198 nsXULPrototypeCache::GetScript(nsIURI* aURI)
200 return mScriptTable.Get(aURI);
203 nsresult
204 nsXULPrototypeCache::PutScript(nsIURI* aURI,
205 JS::Handle<JSScript*> aScriptObject)
207 MOZ_ASSERT(aScriptObject, "Need a non-NULL script");
209 #ifdef DEBUG
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());
218 #endif
220 mScriptTable.Put(aURI, aScriptObject);
222 return NS_OK;
225 nsresult
226 nsXULPrototypeCache::PutXBLDocumentInfo(nsXBLDocumentInfo* aDocumentInfo)
228 nsIURI* uri = aDocumentInfo->DocumentURI();
230 nsRefPtr<nsXBLDocumentInfo> info;
231 mXBLDocTable.Get(uri, getter_AddRefs(info));
232 if (!info) {
233 mXBLDocTable.Put(uri, aDocumentInfo);
235 return NS_OK;
238 static PLDHashOperator
239 FlushSkinXBL(nsIURI* aKey, nsRefPtr<nsXBLDocumentInfo>& aDocInfo, void* aClosure)
241 nsAutoCString str;
242 aKey->GetPath(str);
244 PLDHashOperator ret = PL_DHASH_NEXT;
246 if (!strncmp(str.get(), "/skin", 5)) {
247 ret = PL_DHASH_REMOVE;
250 return ret;
253 static PLDHashOperator
254 FlushSkinSheets(nsIURI* aKey, nsRefPtr<CSSStyleSheet>& aSheet, void* aClosure)
256 nsAutoCString str;
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;
265 return ret;
268 static PLDHashOperator
269 FlushScopedSkinStylesheets(nsIURI* aKey, nsRefPtr<nsXBLDocumentInfo> &aDocInfo, void* aClosure)
271 aDocInfo->FlushSkinStylesheets();
272 return PL_DHASH_NEXT;
275 void
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);
290 void
291 nsXULPrototypeCache::FlushScripts()
293 mScriptTable.Clear();
296 void
297 nsXULPrototypeCache::Flush()
299 mPrototypeTable.Clear();
300 mScriptTable.Clear();
301 mStyleSheetTable.Clear();
302 mXBLDocTable.Clear();
306 bool
307 nsXULPrototypeCache::IsEnabled()
309 return !gDisableXULCache;
312 static bool gDisableXULDiskCache = false; // enabled by default
314 void
315 nsXULPrototypeCache::AbortCaching()
317 #ifdef DEBUG_brendan
318 NS_BREAK();
319 #endif
321 // Flush the XUL cache for good measure, in case we cached a bogus/downrev
322 // script, somehow.
323 Flush();
325 // Clear the cache set
326 mCacheURITable.Clear();
330 nsresult
331 nsXULPrototypeCache::WritePrototype(nsXULPrototypeDocument* aPrototypeDocument)
333 nsresult rv = NS_OK, rv2 = NS_OK;
335 if (!StartupCache::GetSingleton())
336 return NS_OK;
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;
350 nsresult
351 nsXULPrototypeCache::GetInputStream(nsIURI* uri, nsIObjectInputStream** stream)
353 nsAutoCString spec(kXULCachePrefix);
354 nsresult rv = PathifyURI(uri, spec);
355 if (NS_FAILED(rv))
356 return NS_ERROR_NOT_AVAILABLE;
358 nsAutoArrayPtr<char> buf;
359 uint32_t len;
360 nsCOMPtr<nsIObjectInputStream> ois;
361 StartupCache* sc = StartupCache::GetSingleton();
362 if (!sc)
363 return NS_ERROR_NOT_AVAILABLE;
365 rv = sc->GetBuffer(spec.get(), getter_Transfers(buf), &len);
366 if (NS_FAILED(rv))
367 return NS_ERROR_NOT_AVAILABLE;
369 rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(ois));
370 NS_ENSURE_SUCCESS(rv, rv);
371 buf.forget();
373 mInputStreamTable.Put(uri, ois);
375 NS_ADDREF(*stream = ois);
376 return NS_OK;
379 nsresult
380 nsXULPrototypeCache::FinishInputStream(nsIURI* uri) {
381 mInputStreamTable.Remove(uri);
382 return NS_OK;
385 nsresult
386 nsXULPrototypeCache::GetOutputStream(nsIURI* uri, nsIObjectOutputStream** stream)
388 nsresult rv;
389 nsCOMPtr<nsIObjectOutputStream> objectOutput;
390 nsCOMPtr<nsIStorageStream> storageStream;
391 bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
392 if (found) {
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);
398 } else {
399 rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(objectOutput),
400 getter_AddRefs(storageStream),
401 false);
402 NS_ENSURE_SUCCESS(rv, rv);
403 mOutputStreamTable.Put(uri, storageStream);
405 NS_ADDREF(*stream = objectOutput);
406 return NS_OK;
409 nsresult
410 nsXULPrototypeCache::FinishOutputStream(nsIURI* uri)
412 nsresult rv;
413 StartupCache* sc = StartupCache::GetSingleton();
414 if (!sc)
415 return NS_ERROR_NOT_AVAILABLE;
417 nsCOMPtr<nsIStorageStream> storageStream;
418 bool found = mOutputStreamTable.Get(uri, getter_AddRefs(storageStream));
419 if (!found)
420 return NS_ERROR_UNEXPECTED;
421 nsCOMPtr<nsIOutputStream> outputStream
422 = do_QueryInterface(storageStream);
423 outputStream->Close();
425 nsAutoArrayPtr<char> buf;
426 uint32_t len;
427 rv = NewBufferFromStorageStream(storageStream, getter_Transfers(buf),
428 &len);
429 NS_ENSURE_SUCCESS(rv, rv);
431 if (!mCacheURITable.GetEntry(uri)) {
432 nsAutoCString spec(kXULCachePrefix);
433 rv = PathifyURI(uri, spec);
434 if (NS_FAILED(rv))
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);
443 return rv;
446 // We have data if we're in the middle of writing it or we already
447 // have it in the cache.
448 nsresult
449 nsXULPrototypeCache::HasData(nsIURI* uri, bool* exists)
451 if (mOutputStreamTable.Get(uri, nullptr)) {
452 *exists = true;
453 return NS_OK;
455 nsAutoCString spec(kXULCachePrefix);
456 nsresult rv = PathifyURI(uri, spec);
457 if (NS_FAILED(rv)) {
458 *exists = false;
459 return NS_OK;
461 nsAutoArrayPtr<char> buf;
462 uint32_t len;
463 StartupCache* sc = StartupCache::GetSingleton();
464 if (sc)
465 rv = sc->GetBuffer(spec.get(), getter_Transfers(buf), &len);
466 else {
467 *exists = false;
468 return NS_OK;
470 *exists = NS_SUCCEEDED(rv);
471 return NS_OK;
474 static void
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();
485 if (cache)
486 cache->AbortCaching();
490 nsresult
491 nsXULPrototypeCache::BeginCaching(nsIURI* aURI)
493 nsresult rv, tmp;
495 nsAutoCString path;
496 aURI->GetPath(path);
497 if (!StringEndsWith(path, NS_LITERAL_CSTRING(".xul")))
498 return NS_ERROR_NOT_AVAILABLE;
500 StartupCache* startupCache = StartupCache::GetSingleton();
501 if (!startupCache)
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));
517 if (NS_FAILED(rv))
518 return rv;
519 nsAutoCString chromePath;
520 rv = chromeDir->GetNativePath(chromePath);
521 if (NS_FAILED(rv))
522 return rv;
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);
528 if (NS_FAILED(rv))
529 return rv;
530 nsCOMPtr<nsIXULChromeRegistry> chromeReg
531 = do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv);
532 nsAutoCString locale;
533 rv = chromeReg->GetSelectedLocale(package, locale);
534 if (NS_FAILED(rv))
535 return rv;
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),
544 &len);
545 if (NS_SUCCEEDED(rv))
546 rv = NewObjectInputStreamFromBuffer(buf, len, getter_AddRefs(objectInput));
548 if (NS_SUCCEEDED(rv)) {
549 buf.forget();
550 rv = objectInput->ReadCString(fileLocale);
551 tmp = objectInput->ReadCString(fileChromePath);
552 if (NS_FAILED(tmp)) {
553 rv = tmp;
555 if (NS_FAILED(rv) ||
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.
566 return rv;
568 if (NS_FAILED(rv)) {
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),
575 false);
576 if (NS_SUCCEEDED(rv)) {
577 rv = objectOutput->WriteStringZ(locale.get());
578 tmp = objectOutput->WriteStringZ(chromePath.get());
579 if (NS_FAILED(tmp)) {
580 rv = tmp;
582 tmp = objectOutput->Close();
583 if (NS_FAILED(tmp)) {
584 rv = tmp;
586 tmp = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
587 if (NS_FAILED(tmp)) {
588 rv = tmp;
592 if (NS_SUCCEEDED(rv)) {
593 uint64_t len64;
594 rv = inputStream->Available(&len64);
595 if (NS_SUCCEEDED(rv)) {
596 if (len64 <= UINT32_MAX)
597 len = (uint32_t)len64;
598 else
599 rv = NS_ERROR_FILE_TOO_BIG;
603 if (NS_SUCCEEDED(rv)) {
604 buf = new char[len];
605 rv = inputStream->Read(buf, len, &amtRead);
606 if (NS_SUCCEEDED(rv) && len == amtRead)
607 rv = startupCache->PutBuffer(kXULCacheInfoKey, buf, len);
608 else {
609 rv = NS_ERROR_UNEXPECTED;
613 // Failed again, just bail.
614 if (NS_FAILED(rv)) {
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);
624 return NS_OK;
627 static PLDHashOperator
628 MarkXBLInCCGeneration(nsIURI* aKey, nsRefPtr<nsXBLDocumentInfo> &aDocInfo,
629 void* aClosure)
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,
638 void* aClosure)
640 uint32_t* gen = static_cast<uint32_t*>(aClosure);
641 aDoc->MarkInCCGeneration(*gen);
642 return PL_DHASH_NEXT;
645 void
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;
660 void
661 nsXULPrototypeCache::MarkInGC(JSTracer* aTrc)
663 mScriptTable.Enumerate(MarkScriptsInGC, aTrc);