1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
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/. */
9 #include "nsXPCOMStrings.h"
10 #include "mozilla/scache/StartupCache.h"
12 #include "nsAutoPtr.h"
13 #include "nsClassHashtable.h"
14 #include "nsComponentManagerUtils.h"
15 #include "nsDirectoryServiceUtils.h"
16 #include "nsIClassInfo.h"
18 #include "nsIMemoryReporter.h"
19 #include "nsIObserver.h"
20 #include "nsIObserverService.h"
21 #include "nsIOutputStream.h"
22 #include "nsIStartupCache.h"
23 #include "nsIStorageStream.h"
24 #include "nsIStreamBufferAccess.h"
25 #include "nsIStringStream.h"
26 #include "nsISupports.h"
28 #include "nsIZipWriter.h"
29 #include "nsIZipReader.h"
30 #include "nsWeakReference.h"
31 #include "nsZipArchive.h"
32 #include "mozilla/Omnijar.h"
34 #include "mozilla/Telemetry.h"
35 #include "nsThreadUtils.h"
36 #include "nsXULAppAPI.h"
37 #include "nsIProtocolHandler.h"
40 #define SC_ENDIAN "big"
42 #define SC_ENDIAN "little"
45 #if PR_BYTES_PER_WORD == 4
46 #define SC_WORDSIZE "4"
48 #define SC_WORDSIZE "8"
55 GetStartupCacheMappingSize()
57 mozilla::scache::StartupCache
* sc
= mozilla::scache::StartupCache::GetSingleton();
58 return sc
? sc
->SizeOfMapping() : 0;
61 NS_MEMORY_REPORTER_IMPLEMENT(StartupCacheMapping
,
62 "explicit/startup-cache/mapping",
64 nsIMemoryReporter::UNITS_BYTES
,
65 GetStartupCacheMappingSize
,
66 "Memory used to hold the mapping of the startup cache from file. This "
67 "memory is likely to be swapped out shortly after start-up.")
69 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(StartupCacheDataMallocSizeOf
, "startup-cache/data")
72 GetStartupCacheDataSize()
74 mozilla::scache::StartupCache
* sc
= mozilla::scache::StartupCache::GetSingleton();
75 return sc
? sc
->HeapSizeOfIncludingThis(StartupCacheDataMallocSizeOf
) : 0;
78 NS_MEMORY_REPORTER_IMPLEMENT(StartupCacheData
,
79 "explicit/startup-cache/data",
81 nsIMemoryReporter::UNITS_BYTES
,
82 GetStartupCacheDataSize
,
83 "Memory used by the startup cache for things other than the file mapping.")
85 static const char sStartupCacheName
[] = "startupCache." SC_WORDSIZE
"." SC_ENDIAN
;
86 static NS_DEFINE_CID(kZipReaderCID
, NS_ZIPREADER_CID
);
89 StartupCache::GetSingleton()
92 if (XRE_GetProcessType() != GeckoProcessType_Default
) {
93 NS_WARNING("Startup cache is only available in the chrome process");
97 StartupCache::InitSingleton();
100 return StartupCache::gStartupCache
;
104 StartupCache::DeleteSingleton()
106 delete StartupCache::gStartupCache
;
110 StartupCache::InitSingleton()
113 StartupCache::gStartupCache
= new StartupCache();
115 rv
= StartupCache::gStartupCache
->Init();
117 delete StartupCache::gStartupCache
;
118 StartupCache::gStartupCache
= nullptr;
123 StartupCache
* StartupCache::gStartupCache
;
124 bool StartupCache::gShutdownInitiated
;
125 bool StartupCache::gIgnoreDiskCache
;
126 enum StartupCache::TelemetrifyAge
StartupCache::gPostFlushAgeAction
= StartupCache::IGNORE_AGE
;
128 StartupCache::StartupCache()
129 : mArchive(NULL
), mStartupWriteInitiated(false), mWriteThread(NULL
),
130 mMappingMemoryReporter(nullptr), mDataMemoryReporter(nullptr) { }
132 StartupCache::~StartupCache()
138 // Generally, the in-memory table should be empty here,
139 // but an early shutdown means either mTimer didn't run
140 // or the write thread is still running.
143 gStartupCache
= nullptr;
144 (void)::NS_UnregisterMemoryReporter(mMappingMemoryReporter
);
145 (void)::NS_UnregisterMemoryReporter(mDataMemoryReporter
);
146 mMappingMemoryReporter
= nullptr;
147 mDataMemoryReporter
= nullptr;
153 // workaround for bug 653936
154 nsCOMPtr
<nsIProtocolHandler
> jarInitializer(do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX
"jar"));
159 mWriteObjectMap
.Init();
162 // This allows to override the startup cache filename
163 // which is useful from xpcshell, when there is no ProfLDS directory to keep cache in.
164 char *env
= PR_GetEnv("MOZ_STARTUP_CACHE");
166 rv
= NS_NewLocalFile(NS_ConvertUTF8toUTF16(env
), false, getter_AddRefs(mFile
));
168 nsCOMPtr
<nsIFile
> file
;
169 rv
= NS_GetSpecialDirectory("ProfLDS",
170 getter_AddRefs(file
));
172 // return silently, this will fail in mochitests's xpcshell process.
176 rv
= file
->AppendNative(NS_LITERAL_CSTRING("startupCache"));
177 NS_ENSURE_SUCCESS(rv
, rv
);
179 // Try to create the directory if it's not there yet
180 rv
= file
->Create(nsIFile::DIRECTORY_TYPE
, 0777);
181 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_ALREADY_EXISTS
)
184 rv
= file
->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName
));
186 NS_ENSURE_SUCCESS(rv
, rv
);
188 mFile
= do_QueryInterface(file
);
191 NS_ENSURE_TRUE(mFile
, NS_ERROR_UNEXPECTED
);
193 mObserverService
= do_GetService("@mozilla.org/observer-service;1");
195 if (!mObserverService
) {
196 NS_WARNING("Could not get observerService.");
197 return NS_ERROR_UNEXPECTED
;
200 mListener
= new StartupCacheListener();
201 rv
= mObserverService
->AddObserver(mListener
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
,
203 NS_ENSURE_SUCCESS(rv
, rv
);
204 rv
= mObserverService
->AddObserver(mListener
, "startupcache-invalidate",
206 NS_ENSURE_SUCCESS(rv
, rv
);
208 rv
= LoadArchive(RECORD_AGE
);
210 // Sometimes we don't have a cache yet, that's ok.
211 // If it's corrupted, just remove it and start over.
212 if (gIgnoreDiskCache
|| (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_NOT_FOUND
)) {
213 NS_WARNING("Failed to load startupcache file correctly, removing!");
217 mMappingMemoryReporter
= new NS_MEMORY_REPORTER_NAME(StartupCacheMapping
);
218 mDataMemoryReporter
= new NS_MEMORY_REPORTER_NAME(StartupCacheData
);
219 (void)::NS_RegisterMemoryReporter(mMappingMemoryReporter
);
220 (void)::NS_RegisterMemoryReporter(mDataMemoryReporter
);
226 * LoadArchive can be called from the main thread or while reloading cache on write thread.
229 StartupCache::LoadArchive(enum TelemetrifyAge flag
)
231 if (gIgnoreDiskCache
)
232 return NS_ERROR_FAILURE
;
236 nsresult rv
= mFile
->Exists(&exists
);
237 if (NS_FAILED(rv
) || !exists
)
238 return NS_ERROR_FILE_NOT_FOUND
;
240 mArchive
= new nsZipArchive();
241 rv
= mArchive
->OpenArchive(mFile
);
242 if (NS_FAILED(rv
) || flag
== IGNORE_AGE
)
246 if (!mArchive
->GetComment(comment
)) {
251 size_t len
= NS_CStringGetData(comment
, &data
);
252 PRTime creationStamp
;
253 // We might not have a comment if the startup cache file was created
254 // before we started recording creation times in the comment.
255 if (len
== sizeof(creationStamp
)) {
256 memcpy(&creationStamp
, data
, len
);
257 PRTime current
= PR_Now();
258 int64_t diff
= current
- creationStamp
;
260 // We can't use AccumulateTimeDelta here because we have no way of
261 // reifying a TimeStamp from creationStamp.
262 int64_t usec_per_hour
= PR_USEC_PER_SEC
* int64_t(3600);
263 int64_t hour_diff
= (diff
+ usec_per_hour
- 1) / usec_per_hour
;
264 mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_AGE_HOURS
,
274 GetBufferFromZipArchive(nsZipArchive
*zip
, bool doCRC
, const char* id
,
275 char** outbuf
, uint32_t* length
)
278 return NS_ERROR_NOT_AVAILABLE
;
280 nsZipItemPtr
<char> zipItem(zip
, id
, doCRC
);
282 return NS_ERROR_NOT_AVAILABLE
;
284 *outbuf
= zipItem
.Forget();
285 *length
= zipItem
.Length();
289 } /* anonymous namespace */
291 // NOTE: this will not find a new entry until it has been written to disk!
292 // Consumer should take ownership of the resulting buffer.
294 StartupCache::GetBuffer(const char* id
, char** outbuf
, uint32_t* length
)
296 NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
298 if (!mStartupWriteInitiated
) {
300 nsDependentCString
idStr(id
);
301 mTable
.Get(idStr
, &entry
);
303 *outbuf
= new char[entry
->size
];
304 memcpy(*outbuf
, entry
->data
, entry
->size
);
305 *length
= entry
->size
;
310 nsresult rv
= GetBufferFromZipArchive(mArchive
, true, id
, outbuf
, length
);
311 if (NS_SUCCEEDED(rv
))
314 nsRefPtr
<nsZipArchive
> omnijar
= mozilla::Omnijar::GetReader(mozilla::Omnijar::APP
);
315 // no need to checksum omnijarred entries
316 rv
= GetBufferFromZipArchive(omnijar
, false, id
, outbuf
, length
);
317 if (NS_SUCCEEDED(rv
))
320 omnijar
= mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE
);
321 // no need to checksum omnijarred entries
322 return GetBufferFromZipArchive(omnijar
, false, id
, outbuf
, length
);
325 // Makes a copy of the buffer, client retains ownership of inbuf.
327 StartupCache::PutBuffer(const char* id
, const char* inbuf
, uint32_t len
)
329 NS_ASSERTION(NS_IsMainThread(), "Startup cache only available on main thread");
331 if (StartupCache::gShutdownInitiated
) {
332 return NS_ERROR_NOT_AVAILABLE
;
335 nsAutoArrayPtr
<char> data(new char[len
]);
336 memcpy(data
, inbuf
, len
);
338 nsDependentCString
idStr(id
);
339 // Cache it for now, we'll write all together later.
343 mTable
.Get(idStr
, &entry
);
344 NS_ASSERTION(entry
== nullptr, "Existing entry in StartupCache.");
347 nsZipItem
* zipItem
= mArchive
->GetItem(id
);
348 NS_ASSERTION(zipItem
== nullptr, "Existing entry in disk StartupCache.");
352 entry
= new CacheEntry(data
.forget(), len
);
353 mTable
.Put(idStr
, entry
);
354 return ResetStartupWriteTimer();
358 StartupCache::SizeOfMapping()
360 return mArchive
? mArchive
->SizeOfMapping() : 0;
364 StartupCache::HeapSizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf
)
366 // This function could measure more members, but they haven't been found by
367 // DMD to be significant. They can be added later if necessary.
368 return aMallocSizeOf(this) +
369 mTable
.SizeOfExcludingThis(SizeOfEntryExcludingThis
, aMallocSizeOf
);
373 StartupCache::SizeOfEntryExcludingThis(const nsACString
& key
, const nsAutoPtr
<CacheEntry
>& data
,
374 nsMallocSizeOfFun mallocSizeOf
, void *)
376 return data
->SizeOfExcludingThis(mallocSizeOf
);
379 struct CacheWriteHolder
381 nsCOMPtr
<nsIZipWriter
> writer
;
382 nsCOMPtr
<nsIStringInputStream
> stream
;
387 CacheCloseHelper(const nsACString
& key
, nsAutoPtr
<CacheEntry
>& data
,
392 CacheWriteHolder
* holder
= (CacheWriteHolder
*) closure
;
393 nsIStringInputStream
* stream
= holder
->stream
;
394 nsIZipWriter
* writer
= holder
->writer
;
396 stream
->ShareData(data
->data
, data
->size
);
400 rv
= writer
->HasEntry(key
, &hasEntry
);
401 NS_ASSERTION(NS_SUCCEEDED(rv
) && hasEntry
== false,
402 "Existing entry in disk StartupCache.");
404 rv
= writer
->AddEntryStream(key
, holder
->time
, true, stream
, false);
407 NS_WARNING("cache entry deleted but not written to disk.");
409 return PL_DHASH_REMOVE
;
414 * WriteToDisk writes the cache out to disk. Callers of WriteToDisk need to call WaitOnWriteThread
415 * to make sure there isn't a write happening on another thread
418 StartupCache::WriteToDisk()
421 mStartupWriteInitiated
= true;
423 if (!mTable
.IsInitialized() || mTable
.Count() == 0)
426 nsCOMPtr
<nsIZipWriter
> zipW
= do_CreateInstance("@mozilla.org/zipwriter;1");
430 rv
= zipW
->Open(mFile
, PR_RDWR
| PR_CREATE_FILE
);
432 NS_WARNING("could not open zipfile for write");
436 // If we didn't have an mArchive member, that means that we failed to
437 // open the startup cache for reading. Therefore, we need to record
438 // the time of creation in a zipfile comment; this will be useful for
439 // Telemetry statistics.
440 PRTime now
= PR_Now();
443 comment
.Assign((char *)&now
, sizeof(now
));
444 zipW
->SetComment(comment
);
447 nsCOMPtr
<nsIStringInputStream
> stream
448 = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv
);
450 NS_WARNING("Couldn't create string input stream.");
454 CacheWriteHolder holder
;
455 holder
.stream
= stream
;
456 holder
.writer
= zipW
;
459 mTable
.Enumerate(CacheCloseHelper
, &holder
);
461 // Close the archive so Windows doesn't choke.
465 // We succesfully wrote the archive to disk; mark the disk file as trusted
466 gIgnoreDiskCache
= false;
468 // Our reader's view of the archive is outdated now, reload it.
469 LoadArchive(gPostFlushAgeAction
);
475 StartupCache::InvalidateCache()
480 nsresult rv
= mFile
->Remove(false);
481 if (NS_FAILED(rv
) && rv
!= NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
&&
482 rv
!= NS_ERROR_FILE_NOT_FOUND
) {
483 gIgnoreDiskCache
= true;
484 mozilla::Telemetry::Accumulate(Telemetry::STARTUP_CACHE_INVALID
, true);
487 gIgnoreDiskCache
= false;
488 LoadArchive(gPostFlushAgeAction
);
492 StartupCache::IgnoreDiskCache()
494 gIgnoreDiskCache
= true;
496 gStartupCache
->InvalidateCache();
500 * WaitOnWriteThread() is called from a main thread to wait for the worker
501 * thread to finish. However since the same code is used in the worker thread and
502 * main thread, the worker thread can also call WaitOnWriteThread() which is a no-op.
505 StartupCache::WaitOnWriteThread()
507 NS_ASSERTION(NS_IsMainThread(), "Startup cache should only wait for io thread on main thread");
508 if (!mWriteThread
|| mWriteThread
== PR_GetCurrentThread())
511 PR_JoinThread(mWriteThread
);
516 StartupCache::ThreadedWrite(void *aClosure
)
518 PR_SetCurrentThreadName("StartupCache");
519 gStartupCache
->WriteToDisk();
523 * The write-thread is spawned on a timeout(which is reset with every write). This
524 * can avoid a slow shutdown. After writing out the cache, the zipreader is
525 * reloaded on the worker thread.
528 StartupCache::WriteTimeout(nsITimer
*aTimer
, void *aClosure
)
530 gStartupCache
->mWriteThread
= PR_CreateThread(PR_USER_THREAD
,
531 StartupCache::ThreadedWrite
,
539 // We don't want to refcount StartupCache, so we'll just
540 // hold a ref to this and pass it to observerService instead.
541 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheListener
, nsIObserver
)
544 StartupCacheListener::Observe(nsISupports
*subject
, const char* topic
, const PRUnichar
* data
)
546 StartupCache
* sc
= StartupCache::GetSingleton();
550 if (strcmp(topic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
551 // Do not leave the thread running past xpcom shutdown
552 sc
->WaitOnWriteThread();
553 StartupCache::gShutdownInitiated
= true;
554 } else if (strcmp(topic
, "startupcache-invalidate") == 0) {
555 sc
->InvalidateCache();
561 StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream
* aStream
,
562 nsIObjectOutputStream
** aOutStream
)
564 NS_ENSURE_ARG_POINTER(aStream
);
566 StartupCacheDebugOutputStream
* stream
567 = new StartupCacheDebugOutputStream(aStream
, &mWriteObjectMap
);
568 NS_ADDREF(*aOutStream
= stream
);
570 NS_ADDREF(*aOutStream
= aStream
);
577 StartupCache::ResetStartupWriteTimer()
579 mStartupWriteInitiated
= false;
582 mTimer
= do_CreateInstance("@mozilla.org/timer;1", &rv
);
584 rv
= mTimer
->Cancel();
585 NS_ENSURE_SUCCESS(rv
, rv
);
586 // Wait for 10 seconds, then write out the cache.
587 mTimer
->InitWithFuncCallback(StartupCache::WriteTimeout
, this, 60000,
588 nsITimer::TYPE_ONE_SHOT
);
593 StartupCache::RecordAgesAlways()
595 gPostFlushAgeAction
= RECORD_AGE
;
599 // StartupCacheDebugOutputStream implementation
601 NS_IMPL_ISUPPORTS3(StartupCacheDebugOutputStream
, nsIObjectOutputStream
,
602 nsIBinaryOutputStream
, nsIOutputStream
)
605 StartupCacheDebugOutputStream::CheckReferences(nsISupports
* aObject
)
609 nsCOMPtr
<nsIClassInfo
> classInfo
= do_QueryInterface(aObject
);
611 NS_ERROR("aObject must implement nsIClassInfo");
616 rv
= classInfo
->GetFlags(&flags
);
617 NS_ENSURE_SUCCESS(rv
, false);
618 if (flags
& nsIClassInfo::SINGLETON
)
621 nsISupportsHashKey
* key
= mObjectMap
->GetEntry(aObject
);
623 NS_ERROR("non-singleton aObject is referenced multiple times in this"
624 "serialization, we don't support that.");
628 mObjectMap
->PutEntry(aObject
);
632 // nsIObjectOutputStream implementation
634 StartupCacheDebugOutputStream::WriteObject(nsISupports
* aObject
, bool aIsStrongRef
)
636 nsCOMPtr
<nsISupports
> rootObject(do_QueryInterface(aObject
));
638 NS_ASSERTION(rootObject
.get() == aObject
,
639 "bad call to WriteObject -- call WriteCompoundObject!");
640 bool check
= CheckReferences(aObject
);
641 NS_ENSURE_TRUE(check
, NS_ERROR_FAILURE
);
642 return mBinaryStream
->WriteObject(aObject
, aIsStrongRef
);
646 StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports
* aObject
)
648 nsCOMPtr
<nsISupports
> rootObject(do_QueryInterface(aObject
));
650 NS_ASSERTION(rootObject
.get() == aObject
,
651 "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
652 bool check
= CheckReferences(aObject
);
653 NS_ENSURE_TRUE(check
, NS_ERROR_FAILURE
);
654 return mBinaryStream
->WriteSingleRefObject(aObject
);
658 StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports
* aObject
,
662 nsCOMPtr
<nsISupports
> rootObject(do_QueryInterface(aObject
));
664 nsCOMPtr
<nsISupports
> roundtrip
;
665 rootObject
->QueryInterface(aIID
, getter_AddRefs(roundtrip
));
666 NS_ASSERTION(roundtrip
.get() == aObject
,
667 "bad aggregation or multiple inheritance detected by call to "
668 "WriteCompoundObject!");
670 bool check
= CheckReferences(aObject
);
671 NS_ENSURE_TRUE(check
, NS_ERROR_FAILURE
);
672 return mBinaryStream
->WriteCompoundObject(aObject
, aIID
, aIsStrongRef
);
676 StartupCacheDebugOutputStream::WriteID(nsID
const& aID
)
678 return mBinaryStream
->WriteID(aID
);
682 StartupCacheDebugOutputStream::GetBuffer(uint32_t aLength
, uint32_t aAlignMask
)
684 return mBinaryStream
->GetBuffer(aLength
, aAlignMask
);
688 StartupCacheDebugOutputStream::PutBuffer(char* aBuffer
, uint32_t aLength
)
690 mBinaryStream
->PutBuffer(aBuffer
, aLength
);
694 StartupCacheWrapper
* StartupCacheWrapper::gStartupCacheWrapper
= nullptr;
696 NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheWrapper
, nsIStartupCache
)
698 StartupCacheWrapper
* StartupCacheWrapper::GetSingleton()
700 if (!gStartupCacheWrapper
)
701 gStartupCacheWrapper
= new StartupCacheWrapper();
703 NS_ADDREF(gStartupCacheWrapper
);
704 return gStartupCacheWrapper
;
708 StartupCacheWrapper::GetBuffer(const char* id
, char** outbuf
, uint32_t* length
)
710 StartupCache
* sc
= StartupCache::GetSingleton();
712 return NS_ERROR_NOT_INITIALIZED
;
714 return sc
->GetBuffer(id
, outbuf
, length
);
718 StartupCacheWrapper::PutBuffer(const char* id
, const char* inbuf
, uint32_t length
)
720 StartupCache
* sc
= StartupCache::GetSingleton();
722 return NS_ERROR_NOT_INITIALIZED
;
724 return sc
->PutBuffer(id
, inbuf
, length
);
728 StartupCacheWrapper::InvalidateCache()
730 StartupCache
* sc
= StartupCache::GetSingleton();
732 return NS_ERROR_NOT_INITIALIZED
;
734 sc
->InvalidateCache();
739 StartupCacheWrapper::IgnoreDiskCache()
741 StartupCache::IgnoreDiskCache();
746 StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream
* stream
,
747 nsIObjectOutputStream
** outStream
)
749 StartupCache
* sc
= StartupCache::GetSingleton();
751 return NS_ERROR_NOT_INITIALIZED
;
753 return sc
->GetDebugObjectOutputStream(stream
, outStream
);
757 StartupCacheWrapper::StartupWriteComplete(bool *complete
)
759 StartupCache
* sc
= StartupCache::GetSingleton();
761 return NS_ERROR_NOT_INITIALIZED
;
763 sc
->WaitOnWriteThread();
764 *complete
= sc
->mStartupWriteInitiated
&& sc
->mTable
.Count() == 0;
769 StartupCacheWrapper::ResetStartupWriteTimer()
771 StartupCache
* sc
= StartupCache::GetSingleton();
772 return sc
? sc
->ResetStartupWriteTimer() : NS_ERROR_NOT_INITIALIZED
;
776 StartupCacheWrapper::GetObserver(nsIObserver
** obv
) {
777 StartupCache
* sc
= StartupCache::GetSingleton();
779 return NS_ERROR_NOT_INITIALIZED
;
781 NS_ADDREF(*obv
= sc
->mListener
);
786 StartupCacheWrapper::RecordAgesAlways() {
787 StartupCache
*sc
= StartupCache::GetSingleton();
788 return sc
? sc
->RecordAgesAlways() : NS_ERROR_NOT_INITIALIZED
;
791 } // namespace scache
792 } // namespace mozilla