bug 520309, startupcache core r=dwitte sr=bsmedberg a=bsmedberg
authorbhsieh@mozilla.com
Thu, 12 Aug 2010 12:37:44 -0700
changeset 51307 b8e409a955c1105ae26ea1c402c0116b4708b585
parent 51306 4747f09a62e399f5ac0c2bcb16736a047c03b996
child 51308 55dbe9d5910640b9b12da95f3053d1d374724fe7
push id15277
push userdwitte@mozilla.com
push dateTue, 24 Aug 2010 04:18:33 +0000
treeherdermozilla-central@49dc8d6901a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdwitte, bsmedberg, bsmedberg
bugs520309
milestone2.0b5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 520309, startupcache core r=dwitte sr=bsmedberg a=bsmedberg
modules/libjar/nsZipArchive.cpp
modules/libjar/nsZipArchive.h
startupcache/Makefile.in
startupcache/StartupCache.cpp
startupcache/StartupCache.h
startupcache/StartupCacheModule.cpp
startupcache/StartupCacheUtils.cpp
startupcache/StartupCacheUtils.h
startupcache/nsIStartupCache.idl
startupcache/nsStartupCacheUtils.cpp
startupcache/test/Makefile.in
startupcache/test/TestStartupCache.cpp
toolkit/library/libxul-config.mk
toolkit/library/nsStaticXULComponents.cpp
toolkit/toolkit-makefiles.sh
toolkit/toolkit-tiers.mk
toolkit/xre/nsAppRunner.cpp
xpcom/build/nsXPComInit.cpp
xpcom/tests/TestHarness.h
--- a/modules/libjar/nsZipArchive.cpp
+++ b/modules/libjar/nsZipArchive.cpp
@@ -731,16 +731,22 @@ PRUint8* nsZipArchive::GetData(nsZipItem
 
   // -- check if there is enough source data in the file
   if (offset + aItem->Size() > len)
     return nsnull;
 
   return data + offset;
 }
 
+PRBool 
+nsZipArchive::CheckCRC(nsZipItem* aItem, PRUint8* aItemData) {
+  PRUint32 crc = crc32(0, (const unsigned char*)aItemData, aItem->Size());
+  return crc == aItem->CRC32();
+}
+
 //------------------------------------------
 // nsZipArchive constructor and destructor
 //------------------------------------------
 
 nsZipArchive::nsZipArchive() :
   mBuiltSynthetics(false)
 {
   MOZ_COUNT_CTOR(nsZipArchive);
--- a/modules/libjar/nsZipArchive.h
+++ b/modules/libjar/nsZipArchive.h
@@ -191,16 +191,18 @@ public:
 
   /**
    * Get pointer to the data of the item.
    * @param   aItem       Pointer to nsZipItem
    * reutrns null when zip file is corrupt.
    */
   PRUint8* GetData(nsZipItem* aItem);
 
+  PRBool CheckCRC(nsZipItem* aItem, PRUint8* aData);
+
 private:
   //--- private members ---
 
   nsZipItem*    mFiles[ZIP_TABSIZE];
   PLArenaPool   mArena;
 
   // Whether we synthesized the directory entries
   bool          mBuiltSynthetics;
new file mode 100644
--- /dev/null
+++ b/startupcache/Makefile.in
@@ -0,0 +1,82 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Startup Cache.
+#
+# The Initial Developer of the Original Code is
+#  Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Benedict Hsieh <bhsieh@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+
+DEPTH = ..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+DIRS = $(NULL)
+
+ifdef ENABLE_TESTS
+TOOL_DIRS += test
+endif
+
+MODULE = startupcache
+MODULE_NAME = StartupCacheModule
+LIBRARY_NAME = startupcache
+SHORT_LIBNAME = scache
+EXPORT_LIBRARY = 1
+LIBXUL_LIBRARY = 1
+IS_COMPONENT = 1
+GRE_MODULE = 1
+
+CPPSRCS = StartupCache.cpp \
+  StartupCacheUtils.cpp \
+  StartupCacheModule.cpp \
+  $(NULL)
+
+EXPORTS_NAMESPACES = mozilla/scache
+EXPORTS_mozilla/scache = StartupCache.h \
+  StartupCacheUtils.h \
+  $(NULL)
+
+XPIDLSRCS = nsIStartupCache.idl \
+  $(NULL)
+
+EXTRA_DSO_LDOPTS += \
+  $(LIBS_DIR) \
+  $(ZLIB_LIBS) \
+  $(MOZ_COMPONENT_LIBS) \
+  $(NULL)
+
+
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/startupcache/StartupCache.cpp
@@ -0,0 +1,633 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Startup Cache.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation <http://www.mozilla.org/>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "prio.h"
+#include "prtypes.h"
+#include "pldhash.h"
+#include "mozilla/scache/StartupCache.h"
+
+#include "nsAutoPtr.h"
+#include "nsClassHashtable.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIClassInfo.h"
+#include "nsIFile.h"
+#include "nsILocalFile.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsIStartupCache.h"
+#include "nsIStorageStream.h"
+#include "nsIStreamBufferAccess.h"
+#include "nsIStringStream.h"
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsIZipWriter.h"
+#include "nsIZipReader.h"
+#include "nsWeakReference.h"
+#include "nsZipArchive.h"
+
+#ifdef IS_BIG_ENDIAN
+#define SC_ENDIAN "big"
+#else
+#define SC_ENDIAN "little"
+#endif
+
+#if PR_BYTES_PER_WORD == 4
+#define SC_WORDSIZE "4"
+#else
+#define SC_WORDSIZE "8"
+#endif
+
+namespace mozilla {
+namespace scache {
+
+static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
+static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
+
+StartupCache*
+StartupCache::GetSingleton() 
+{
+  if (!gStartupCache)
+    StartupCache::InitSingleton();
+
+  return StartupCache::gStartupCache;
+}
+
+void
+StartupCache::DeleteSingleton()
+{
+  delete StartupCache::gStartupCache;
+}
+
+nsresult
+StartupCache::InitSingleton() 
+{
+  nsresult rv;
+  StartupCache::gStartupCache = new StartupCache();
+
+  rv = StartupCache::gStartupCache->Init();
+  if (NS_FAILED(rv)) {
+    delete StartupCache::gStartupCache;
+  }
+  return rv;
+}
+
+StartupCache* StartupCache::gStartupCache;
+PRBool StartupCache::gShutdownInitiated;
+
+StartupCache::StartupCache() 
+  : mArchive(NULL), mStartupWriteInitiated(PR_FALSE) { }
+
+StartupCache::~StartupCache() 
+{
+  if (mTable.Count() > 0) {
+    NS_WARNING("Shutting down with entries in startupcache, these will be lost");
+  }
+  gStartupCache = nsnull;
+}
+
+nsresult
+StartupCache::Init() 
+{
+  nsresult rv;
+  mTable.Init();
+#ifdef DEBUG
+  mWriteObjectMap.Init();
+#endif
+
+  mZipW = do_CreateInstance("@mozilla.org/zipwriter;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIFile> file;
+  rv = NS_GetSpecialDirectory("ProfLDS",
+                              getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    // return silently, this will fail in mochitests's xpcshell process.
+    return rv;
+  }
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING("startupCache"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Try to create the directory if it's not there yet
+  rv = file->Create(nsIFile::DIRECTORY_TYPE, 0777);
+  if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
+    return rv;
+
+  rv = file->AppendNative(NS_LITERAL_CSTRING(sStartupCacheName));
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  mFile = do_QueryInterface(file);
+  NS_ENSURE_TRUE(mFile, NS_ERROR_UNEXPECTED);
+
+  mObserverService = do_GetService("@mozilla.org/observer-service;1");
+  
+  if (!mObserverService) {
+    NS_WARNING("Could not get observerService.");
+    return NS_ERROR_UNEXPECTED;
+  }
+  
+  mListener = new StartupCacheListener();  
+  rv = mObserverService->AddObserver(mListener, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
+                                     PR_FALSE);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = LoadArchive();
+  
+  // Sometimes we don't have a cache yet, that's ok.
+  // If it's corrupted, just remove it and start over.
+  if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+    NS_WARNING("Failed to load startupcache file correctly, removing!");
+    InvalidateCache();
+  }
+
+  mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  // Wait for 10 seconds, then write out the cache.
+  rv = mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, this, 10000,
+                                    nsITimer::TYPE_ONE_SHOT);
+
+  return rv;
+}
+
+nsresult
+StartupCache::LoadArchive() 
+{
+  PRBool exists;
+  mArchive = NULL;
+  nsresult rv = mFile->Exists(&exists);
+  if (NS_FAILED(rv) || !exists)
+    return NS_ERROR_FILE_NOT_FOUND;
+  
+  mArchive = new nsZipArchive();
+  return mArchive->OpenArchive(mFile);
+}
+
+// NOTE: this will not find a new entry until it has been written to disk!
+// Consumer should take ownership of the resulting buffer.
+nsresult
+StartupCache::GetBuffer(const char* id, char** outbuf, PRUint32* length) 
+{
+  PRBool exists;
+  char* data = NULL;
+  PRUint32 len;
+
+  if (!mStartupWriteInitiated) {
+    CacheEntry* entry; 
+    nsDependentCString idStr(id);
+    mTable.Get(idStr, &entry);
+    if (entry) {
+      data = entry->data;
+      len = entry->size;
+    }
+  }
+
+  if (!data && mArchive) {
+    nsZipItem* zipItem = mArchive->GetItem(id);
+    if (zipItem) {
+      PRUint8* itemData = mArchive->GetData(zipItem);
+      if (!itemData || !mArchive->CheckCRC(zipItem, itemData)) {
+        NS_WARNING("StartupCache file corrupted!");
+        InvalidateCache();
+        return NS_ERROR_FILE_CORRUPTED;
+      }
+
+      len = zipItem->Size();
+      data = (char*) itemData;
+    }
+  }
+
+  if (data) {
+    *outbuf = new char[len];
+    memcpy(*outbuf, data, len);
+    *length = len;
+    return NS_OK;
+  }
+
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+// Makes a copy of the buffer, client retains ownership of inbuf.
+nsresult
+StartupCache::PutBuffer(const char* id, const char* inbuf, PRUint32 len) 
+{
+  nsresult rv;
+
+  if (StartupCache::gShutdownInitiated) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsAutoArrayPtr<char> data(new char[len]);
+  memcpy(data, inbuf, len);
+
+  nsDependentCString idStr(id);
+  if (!mStartupWriteInitiated) {
+    // Cache it for now, we'll write all together later.
+    CacheEntry* entry; 
+
+#ifdef DEBUG
+    mTable.Get(idStr, &entry);
+    NS_ASSERTION(entry == nsnull, "Existing entry in StartupCache.");
+
+    if (mArchive) {
+      nsZipItem* zipItem = mArchive->GetItem(id);
+      NS_ASSERTION(zipItem == nsnull, "Existing entry in disk StartupCache.");
+    }
+#endif
+
+    entry = new CacheEntry(data.forget(), len);
+    mTable.Put(idStr, entry);
+    return NS_OK;
+  }
+  
+  rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
+  NS_ENSURE_SUCCESS(rv, rv);  
+
+  // XXX We need to think about whether to write this out every time,
+  // or somehow detect a good time to write.  We need to finish writing
+  // before shutdown though, and writing also requires a reload of the
+  // reader's archive, which probably can't handle having the underlying
+  // file change underneath it. Potentially could reload on the next
+  // read request, if this is a problem. See Bug 586859.
+#ifdef DEBUG
+  PRBool hasEntry;
+  rv = mZipW->HasEntry(idStr, &hasEntry);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ASSERTION(hasEntry == PR_FALSE, "Existing entry in disk StartupCache.");
+#endif
+
+  nsCOMPtr<nsIStringInputStream> stream
+    = do_CreateInstance("@mozilla.org/io/string-input-stream;1",
+                        &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = stream->AdoptData(data, len);
+  NS_ENSURE_SUCCESS(rv, rv);
+  data.forget();
+  
+  rv = mZipW->AddEntryStream(idStr, 0, 0, stream, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Close the archive so Windows doesn't choke.
+  mArchive = NULL;
+  rv = mZipW->Close();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // our reader's view of the archive is outdated now, reload it.
+  return LoadArchive();
+}
+
+struct CacheWriteHolder
+{
+  nsCOMPtr<nsIZipWriter> writer;
+  nsCOMPtr<nsIStringInputStream> stream;
+};
+
+PLDHashOperator
+CacheCloseHelper(const nsACString& key, nsAutoPtr<CacheEntry>& data, 
+                 void* closure) 
+{
+  nsresult rv;
+ 
+  CacheWriteHolder* holder = (CacheWriteHolder*) closure;  
+  nsIStringInputStream* stream = holder->stream;
+  nsIZipWriter* writer = holder->writer;
+
+  stream->ShareData(data->data, data->size);
+
+#ifdef DEBUG
+  PRBool hasEntry;
+  rv = writer->HasEntry(key, &hasEntry);
+  NS_ASSERTION(NS_SUCCEEDED(rv) && hasEntry == PR_FALSE, 
+               "Existing entry in disk StartupCache.");
+#endif
+  rv = writer->AddEntryStream(key, 0, 0, stream, false);
+  
+  if (NS_FAILED(rv)) {
+    NS_WARNING("cache entry deleted but not written to disk.");
+  }
+  return PL_DHASH_REMOVE;
+}
+
+void
+StartupCache::WriteToDisk() 
+{
+  nsresult rv;
+  mStartupWriteInitiated = PR_TRUE;
+
+  if (gShutdownInitiated) {
+    NS_WARNING("xpcom-shutdown recieved before initial write, will not write");
+    // The dtor would clear for us, but we clear here so that StartupWriteComplete()
+    // will return true instead of waiting for the table to finish clearing.
+    // This mechanism will change when IO is moved off-thread (bug 586859).
+    mTable.Clear();
+    return;
+  }
+
+  if (mTable.Count() == 0)
+    return;
+
+  rv = mZipW->Open(mFile, PR_RDWR | PR_CREATE_FILE);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("could not open zipfile for write");
+    return;
+  } 
+
+  nsCOMPtr<nsIStringInputStream> stream 
+    = do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Couldn't create string input stream.");
+    return;
+  }
+
+  CacheWriteHolder holder;
+  holder.stream = stream;
+  holder.writer = mZipW;
+
+  mTable.Enumerate(CacheCloseHelper, &holder);
+
+  // Close the archive so Windows doesn't choke.
+  mArchive = NULL;
+  mZipW->Close();
+      
+  // our reader's view of the archive is outdated now, reload it.
+  LoadArchive();
+  
+  return;
+}
+
+void
+StartupCache::InvalidateCache() 
+{
+  mTable.Clear();
+  mArchive = NULL;
+
+  // This is usually closed, but it's possible to get into
+  // an inconsistent state.
+  mZipW->Close();
+  mFile->Remove(false);
+  LoadArchive();
+}
+
+void
+StartupCache::WriteTimeout(nsITimer *aTimer, void *aClosure)
+{
+  StartupCache* sc = (StartupCache*) aClosure;
+  sc->WriteToDisk();
+}
+
+// We don't want to refcount StartupCache, so we'll just
+// hold a ref to this and pass it to observerService instead.
+NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheListener, nsIObserver)
+
+nsresult
+StartupCacheListener::Observe(nsISupports *subject, const char* topic, const PRUnichar* data)
+{
+  nsresult rv = NS_OK;
+  if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+    StartupCache::gShutdownInitiated = PR_TRUE;
+  }
+  return rv;
+} 
+
+nsresult
+StartupCache::GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
+                                         nsIObjectOutputStream** aOutStream) 
+{
+  NS_ENSURE_ARG_POINTER(aStream);
+#ifdef DEBUG
+  StartupCacheDebugOutputStream* stream
+    = new StartupCacheDebugOutputStream(aStream, &mWriteObjectMap);
+  NS_ADDREF(*aOutStream = stream);
+#else
+  NS_ADDREF(*aOutStream = aStream);
+#endif
+  
+  return NS_OK;
+}
+
+// StartupCacheDebugOutputStream implementation
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS3(StartupCacheDebugOutputStream, nsIObjectOutputStream, 
+                   nsIBinaryOutputStream, nsIOutputStream)
+
+PRBool
+StartupCacheDebugOutputStream::CheckReferences(nsISupports* aObject)
+{
+  nsresult rv;
+  
+  nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(aObject);
+  if (!classInfo) {
+    NS_ERROR("aObject must implement nsIClassInfo");
+    return PR_FALSE;
+  }
+  
+  PRUint32 flags;
+  rv = classInfo->GetFlags(&flags);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (flags & nsIClassInfo::SINGLETON)
+    return PR_TRUE;
+  
+  nsISupportsHashKey* key = mObjectMap->GetEntry(aObject);
+  if (key) {
+    NS_ERROR("non-singleton aObject is referenced multiple times in this" 
+                  "serialization, we don't support that.");
+    return PR_FALSE;
+  }
+
+  mObjectMap->PutEntry(aObject);
+  return PR_TRUE;
+}
+
+// nsIObjectOutputStream implementation
+nsresult
+StartupCacheDebugOutputStream::WriteObject(nsISupports* aObject, PRBool aIsStrongRef)
+{
+  nsresult rv;
+  
+  nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
+  
+  NS_ASSERTION(rootObject.get() == aObject,
+               "bad call to WriteObject -- call WriteCompoundObject!");
+  PRBool check = CheckReferences(aObject);
+  NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
+  return mBinaryStream->WriteObject(aObject, aIsStrongRef);
+}
+
+nsresult
+StartupCacheDebugOutputStream::WriteSingleRefObject(nsISupports* aObject)
+{
+  nsresult rv;
+  nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
+  
+  NS_ASSERTION(rootObject.get() == aObject,
+               "bad call to WriteSingleRefObject -- call WriteCompoundObject!");
+  PRBool check = CheckReferences(aObject);
+  NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
+  return mBinaryStream->WriteSingleRefObject(aObject);
+}
+
+nsresult
+StartupCacheDebugOutputStream::WriteCompoundObject(nsISupports* aObject,
+                                                const nsIID& aIID,
+                                                PRBool aIsStrongRef)
+{
+  nsresult rv;
+  nsCOMPtr<nsISupports> rootObject(do_QueryInterface(aObject));
+  
+  nsCOMPtr<nsISupports> roundtrip;
+  rootObject->QueryInterface(aIID, getter_AddRefs(roundtrip));
+  NS_ASSERTION(roundtrip.get() == aObject,
+               "bad aggregation or multiple inheritance detected by call to "
+               "WriteCompoundObject!");
+
+  PRBool check = CheckReferences(aObject);
+  NS_ENSURE_TRUE(check, NS_ERROR_FAILURE);
+  return mBinaryStream->WriteCompoundObject(aObject, aIID, aIsStrongRef);
+}
+
+nsresult
+StartupCacheDebugOutputStream::WriteID(nsID const& aID) 
+{
+  return mBinaryStream->WriteID(aID);
+}
+
+char*
+StartupCacheDebugOutputStream::GetBuffer(PRUint32 aLength, PRUint32 aAlignMask)
+{
+  return mBinaryStream->GetBuffer(aLength, aAlignMask);
+}
+
+void
+StartupCacheDebugOutputStream::PutBuffer(char* aBuffer, PRUint32 aLength)
+{
+  mBinaryStream->PutBuffer(aBuffer, aLength);
+}
+#endif //DEBUG
+
+StartupCacheWrapper* StartupCacheWrapper::gStartupCacheWrapper = nsnull;
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(StartupCacheWrapper, nsIStartupCache)
+
+StartupCacheWrapper* StartupCacheWrapper::GetSingleton() 
+{
+  if (!gStartupCacheWrapper)
+    gStartupCacheWrapper = new StartupCacheWrapper();
+
+  NS_ADDREF(gStartupCacheWrapper);
+  return gStartupCacheWrapper;
+}
+
+nsresult 
+StartupCacheWrapper::GetBuffer(const char* id, char** outbuf, PRUint32* length) 
+{
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  return sc->GetBuffer(id, outbuf, length);
+}
+
+nsresult
+StartupCacheWrapper::PutBuffer(const char* id, char* inbuf, PRUint32 length) 
+{
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  return sc->PutBuffer(id, inbuf, length);
+}
+
+nsresult
+StartupCacheWrapper::InvalidateCache() 
+{
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  sc->InvalidateCache();
+  return NS_OK;
+}
+
+nsresult 
+StartupCacheWrapper::GetDebugObjectOutputStream(nsIObjectOutputStream* stream,
+                                                nsIObjectOutputStream** outStream) 
+{
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  return sc->GetDebugObjectOutputStream(stream, outStream);
+}
+
+nsresult
+StartupCacheWrapper::StartupWriteComplete(PRBool *complete)
+{  
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  *complete = sc->mStartupWriteInitiated && sc->mTable.Count() == 0;
+  return NS_OK;
+}
+
+nsresult
+StartupCacheWrapper::ResetStartupWriteTimer()
+{
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  sc->mStartupWriteInitiated = PR_FALSE;
+  sc->mTimer->InitWithFuncCallback(StartupCache::WriteTimeout, sc, 10000,
+                                   nsITimer::TYPE_ONE_SHOT);
+  return NS_OK;
+}
+
+nsresult
+StartupCacheWrapper::GetObserver(nsIObserver** obv) {
+  StartupCache* sc = StartupCache::GetSingleton();
+  if (!sc) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  NS_ADDREF(*obv = sc->mListener);
+  return NS_OK;
+}
+
+} // namespace scache
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/startupcache/StartupCache.h
@@ -0,0 +1,223 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Startup Cache.
+ *
+ * The Initial Developer of the Original Code is 
+ * The Mozilla Foundation <http://www.mozilla.org/>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef StartupCache_h_
+#define StartupCache_h_
+
+#include "prio.h"
+#include "prtypes.h"
+
+#include "nsClassHashtable.h"
+#include "nsIZipWriter.h"
+#include "nsIZipReader.h"
+#include "nsComponentManagerUtils.h"
+#include "nsZipArchive.h"
+#include "nsIStartupCache.h"
+#include "nsIStorageStream.h"
+#include "nsITimer.h"
+#include "nsIObserverService.h"
+#include "nsIObserver.h"
+#include "nsIOutputStream.h"
+#include "nsIFile.h"
+
+/**
+ * The StartupCache is a persistent cache of simple key-value pairs,
+ * where the keys are null-terminated c-strings and the values are 
+ * arbitrary data, passed as a (char*, size) tuple. 
+ *
+ * Clients should use the GetSingleton() static method to access the cache. It 
+ * will be available from the end of XPCOM init (NS_InitXPCOM3 in nsXPComInit.cpp), 
+ * until XPCOM shutdown begins. The GetSingleton() method will return null if the cache
+ * is unavailable. The cache is only provided for libxul builds --
+ * it will fail to link in non-libxul builds. The XPCOM interface is provided
+ * only to allow compiled-code tests; clients should avoid using it.
+ *
+ * The API provided is very simple: GetBuffer() returns a buffer that was previously
+ * stored in the cache (if any), and PutBuffer() inserts a buffer into the cache.
+ * GetBuffer returns a new buffer, and the caller must take ownership of it.
+ * PutBuffer will assert if the client attempts to insert a buffer with the same name as
+ * an existing entry. The cache makes a copy of the passed-in buffer, so client
+ * retains ownership.
+ *
+ * InvalidateCache() may be called if a client suspects data corruption 
+ * or wishes to invalidate for any other reason. This will remove all existing cache data.
+ * Finally, getDebugObjectOutputStream() allows debug code to wrap an objectstream
+ * with a debug objectstream, to check for multiply-referenced objects. These will
+ * generally fail to deserialize correctly, unless they are stateless singletons or the 
+ * client maintains their own object data map for deserialization.
+ *
+ * Writes before the final-ui-startup notification are placed in an intermediate
+ * cache in memory, then written out to disk at a later time, to get writes off the
+ * startup path. In any case, clients should not rely on being able to GetBuffer()
+ * data that is written to the cache, since it may not have been written to disk or
+ * another client may have invalidated the cache. In other words, it should be used as
+ * a cache only, and not a reliable persistent store.
+ *
+ * Some utility functions are provided in StartupCacheUtils. These functions wrap the
+ * buffers into object streams, which may be useful for serializing objects. Note
+ * the above caution about multiply-referenced objects, though -- the streams are just
+ * as 'dumb' as the underlying buffers about multiply-referenced objects. They just
+ * provide some convenience in writing out data.
+ */
+
+namespace mozilla {
+namespace scache {
+
+struct CacheEntry 
+{
+  nsAutoArrayPtr<char> data;
+  PRUint32 size;
+
+  CacheEntry() : data(nsnull), size(0) { }
+
+  // Takes possession of buf
+  CacheEntry(char* buf, PRUint32 len) : data(buf), size(len) { }
+
+  ~CacheEntry()
+  {
+  }
+};
+
+// We don't want to refcount StartupCache, and ObserverService wants to
+// refcount its listeners, so we'll let it refcount this instead.
+class StartupCacheListener : public nsIObserver
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+};
+
+class StartupCache
+{
+
+friend class StartupCacheListener;
+friend class StartupCacheWrapper;
+                                
+public:
+
+  // StartupCache methods. See above comments for a more detailed description.
+
+  // Returns a buffer that was previously stored, caller takes ownership. 
+  nsresult GetBuffer(const char* id, char** outbuf, PRUint32* length);
+
+  // Stores a buffer. Caller keeps ownership, we make a copy.
+  nsresult PutBuffer(const char* id, const char* inbuf, PRUint32 length);
+
+  // Removes the cache file.
+  void InvalidateCache();
+
+  // In DEBUG builds, returns a stream that will attempt to check for
+  // and disallow multiple writes of the same object.
+  nsresult GetDebugObjectOutputStream(nsIObjectOutputStream* aStream,
+                                      nsIObjectOutputStream** outStream);
+
+  static StartupCache* GetSingleton();
+  static void DeleteSingleton();
+
+private:
+  StartupCache();
+  ~StartupCache();
+
+  nsresult LoadArchive();
+  nsresult Init();
+  void WriteToDisk();
+
+  static nsresult InitSingleton();
+  static void WriteTimeout(nsITimer *aTimer, void *aClosure);
+
+  nsClassHashtable<nsCStringHashKey, CacheEntry> mTable;
+  nsCOMPtr<nsIZipWriter> mZipW;
+  nsAutoPtr<nsZipArchive> mArchive;
+  nsCOMPtr<nsILocalFile> mFile;
+  
+  nsCOMPtr<nsIObserverService> mObserverService;
+  nsRefPtr<StartupCacheListener> mListener;
+  nsCOMPtr<nsITimer> mTimer;
+
+  PRBool mStartupWriteInitiated;
+
+  static StartupCache *gStartupCache;
+  static PRBool gShutdownInitiated;
+
+#ifdef DEBUG
+  nsTHashtable<nsISupportsHashKey> mWriteObjectMap;
+#endif
+};
+
+// This debug outputstream attempts to detect if clients are writing multiple
+// references to the same object. We only support that if that object
+// is a singleton.
+#ifdef DEBUG
+class StartupCacheDebugOutputStream
+  : public nsIObjectOutputStream
+{  
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBJECTOUTPUTSTREAM
+
+  StartupCacheDebugOutputStream (nsIObjectOutputStream* binaryStream,
+                                   nsTHashtable<nsISupportsHashKey>* objectMap)
+  : mBinaryStream(binaryStream), mObjectMap(objectMap) { }
+  
+  NS_FORWARD_SAFE_NSIBINARYOUTPUTSTREAM(mBinaryStream)
+  NS_FORWARD_SAFE_NSIOUTPUTSTREAM(mBinaryStream)
+  
+  PRBool CheckReferences(nsISupports* aObject);
+  
+  nsCOMPtr<nsIObjectOutputStream> mBinaryStream;
+  nsTHashtable<nsISupportsHashKey> *mObjectMap;
+};
+#endif // DEBUG
+
+// XPCOM wrapper interface provided for tests only.
+#define NS_STARTUPCACHE_CID \
+      {0xae4505a9, 0x87ab, 0x477c, \
+      {0xb5, 0x77, 0xf9, 0x23, 0x57, 0xed, 0xa8, 0x84}}
+// contract id: "@mozilla.org/startupcache/cache;1"
+
+class StartupCacheWrapper 
+  : public nsIStartupCache
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISTARTUPCACHE
+
+  static StartupCacheWrapper* GetSingleton();
+  static StartupCacheWrapper *gStartupCacheWrapper;
+};
+
+} // namespace scache
+} // namespace mozilla
+#endif //StartupCache_h_
new file mode 100644
--- /dev/null
+++ b/startupcache/StartupCacheModule.cpp
@@ -0,0 +1,83 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Startup Cache.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation <http://www.mozilla.org/>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <string.h>
+
+#include "nscore.h"
+#include "pratom.h"
+#include "prmem.h"
+#include "prio.h"
+#include "plstr.h"
+#include "prlog.h"
+
+#include "nsID.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIModule.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/scache/StartupCache.h"
+
+using namespace mozilla::scache;
+
+// XXX Need help with guard for ENABLE_TEST
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(StartupCacheWrapper,
+                                         StartupCacheWrapper::GetSingleton)
+NS_DEFINE_NAMED_CID(NS_STARTUPCACHE_CID);
+
+static const mozilla::Module::CIDEntry kStartupCacheCIDs[] = {
+    { &kNS_STARTUPCACHE_CID, false, NULL, StartupCacheWrapperConstructor },
+    { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kStartupCacheContracts[] = {
+    { "@mozilla.org/startupcache/cache;1", &kNS_STARTUPCACHE_CID },
+    { NULL }
+};
+
+static const mozilla::Module kStartupCacheModule = {
+    mozilla::Module::kVersion,
+    kStartupCacheCIDs,
+    kStartupCacheContracts,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+
+NSMODULE_DEFN(StartupCacheModule) = &kStartupCacheModule;
new file mode 100644
--- /dev/null
+++ b/startupcache/StartupCacheUtils.cpp
@@ -0,0 +1,90 @@
+
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsIStringStream.h"
+#include "nsAutoPtr.h"
+#include "StartupCacheUtils.h"
+#include "mozilla/scache/StartupCache.h"
+
+namespace mozilla {
+namespace scache {
+
+NS_EXPORT nsresult
+NS_NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len, 
+                                  nsIObjectInputStream** stream)
+{
+  nsCOMPtr<nsIStringInputStream> stringStream
+    = do_CreateInstance("@mozilla.org/io/string-input-stream;1");
+  nsCOMPtr<nsIObjectInputStream> objectInput 
+    = do_CreateInstance("@mozilla.org/binaryinputstream;1");
+  
+  stringStream->AdoptData(buffer, len);
+  objectInput->SetInputStream(stringStream);
+  
+  objectInput.forget(stream);
+  return NS_OK;
+}
+
+NS_EXPORT nsresult
+NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+                                       nsIStorageStream** stream)
+{
+  nsresult rv;
+  nsCOMPtr<nsIStorageStream> storageStream
+    = do_CreateInstance("@mozilla.org/storagestream;1");
+  
+  rv = storageStream->Init(256, PR_UINT32_MAX, nsnull);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsCOMPtr<nsIObjectOutputStream> objectOutput
+    = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+  nsCOMPtr<nsIOutputStream> outputStream
+    = do_QueryInterface(storageStream);
+  
+  objectOutput->SetOutputStream(outputStream);
+  
+#ifdef DEBUG
+  // Wrap in debug stream to detect unsupported writes of 
+  // multiply-referenced non-singleton objects
+  StartupCache* sc = StartupCache::GetSingleton();
+  NS_ENSURE_TRUE(sc, NS_ERROR_UNEXPECTED);
+  nsCOMPtr<nsIObjectOutputStream> debugStream;
+  sc->GetDebugObjectOutputStream(objectOutput, getter_AddRefs(debugStream));
+  debugStream.forget(wrapperStream);
+#else
+  objectOutput.forget(wrapperStream);
+#endif
+  
+  storageStream.forget(stream);
+  return NS_OK;
+}
+
+NS_EXPORT nsresult
+NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, 
+                              char** buffer, PRUint32* len) 
+{
+  nsresult rv;
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  PRUint32 avail, read;
+  rv = inputStream->Available(&avail);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsAutoArrayPtr<char> temp (new char[avail]);
+  rv = inputStream->Read(temp, avail, &read);
+  if (NS_SUCCEEDED(rv) && avail != read)
+    rv = NS_ERROR_UNEXPECTED;
+  
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  
+  *len = avail;
+  *buffer = temp.forget();
+  return NS_OK;
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/startupcache/StartupCacheUtils.h
@@ -0,0 +1,63 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Startup Cache.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation <http://www.mozilla.org/>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#ifndef nsStartupCacheUtils_h_
+#define nsStartupCacheUtils_h_
+
+#include "nsIStorageStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+namespace mozilla {
+namespace scache {
+
+NS_EXPORT nsresult
+NS_NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len, 
+                                  nsIObjectInputStream** stream);
+
+// We can't retrieve the wrapped stream from the objectOutputStream later,
+// so we return it here.
+NS_EXPORT nsresult
+NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+                                       nsIStorageStream** stream);
+
+NS_EXPORT nsresult
+NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, 
+                              char** buffer, PRUint32* len);
+}
+}
+#endif //nsStartupCacheUtils_h_
new file mode 100644
--- /dev/null
+++ b/startupcache/nsIStartupCache.idl
@@ -0,0 +1,76 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Corporation startup cache code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIInputStream.idl"
+#include "nsISupports.idl"
+#include "nsIObserver.idl"
+#include "nsIObjectOutputStream.idl"
+
+[uuid(de798fab-af49-4a61-8144-81550986e1da)]
+interface nsIStartupCache : nsISupports
+{
+
+  /** This interface is provided for testing purposes only, basically
+   *  just to solve link vagaries. See docs in StartupCache.h
+   *  GetBuffer, PutBuffer, and InvalidateCache act as described 
+   *  in that file. */
+
+  PRUint32 getBuffer(in string aID, out charPtr aBuffer);
+  void putBuffer(in string aID, in charPtr aBuffer, 
+                            in PRUint32 aLength);
+ 
+  void invalidateCache();
+  
+  /** In debug builds, wraps this object output stream with a stream that will 
+   *  detect and prevent the write of a multiply-referenced non-singleton object 
+   *  during serialization. In non-debug, returns an add-ref'd pointer to
+   *  original stream, unwrapped. */
+  nsIObjectOutputStream getDebugObjectOutputStream(in nsIObjectOutputStream aStream);
+
+  /* Allows clients to check whether the one-time writeout after startup 
+   * has finished yet, and also to set this variable as needed (so test
+   * code can fire mulitple startup writes if needed).
+   */
+  boolean startupWriteComplete();
+  void resetStartupWriteTimer();
+
+  /* Allows clients to simulate the behavior of ObserverService. */
+  readonly attribute nsIObserver observer;
+};
+
new file mode 100644
--- /dev/null
+++ b/startupcache/nsStartupCacheUtils.cpp
@@ -0,0 +1,124 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Startup Cache.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation <http://www.mozilla.org/>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsStartupCacheUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIInputStream.h"
+#include "nsIStorageStream.h"
+#include "nsIStringStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+nsresult
+NS_NewObjectInputStreamFromBuffer(char* buffer, int len, 
+                                  nsIObjectInputStream** stream)
+{
+  nsCOMPtr<nsIStringInputStream> stringStream
+    = do_CreateInstance("@mozilla.org/io/string-input-stream;1");
+  if (!stringStream)
+    return NS_ERROR_OUT_OF_MEMORY;
+  nsCOMPtr<nsIObjectInputStream> objectInput 
+    = do_CreateInstance("@mozilla.org/binaryinputstream;1");
+  if (!objectInput)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  stringStream->AdoptData(buffer, len);
+  objectInput->SetInputStream(stringStream);
+
+  NS_ADDREF(*stream = objectInput);
+  return NS_OK;
+}
+
+// This is questionable API name and design, but we can't
+// retrieve the wrapped stream from the objectOutputStream later...
+nsresult
+NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+                                       nsIStorageStream** stream)
+{
+  nsCOMPtr<nsIStorageStream> storageStream;
+  nsresult rv = NS_NewStorageStream(256, (PRUint32)-1, 
+                                    getter_AddRefs(storageStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIObjectOutputStream> objectOutput
+    = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+  if (!objectOutput)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  nsCOMPtr<nsIOutputStream> outputStream
+    = do_QueryInterface(storageStream);
+
+  objectOutput->SetOutputStream(outputStream);
+  NS_ADDREF(*wrapperStream = objectOutput);
+  NS_ADDREF(*stream = storageStream);
+  return NS_OK;
+}
+
+nsresult
+NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, 
+                              char** buffer, int* len)
+{
+  nsresult rv;
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRUint32 avail, read;
+  rv = inputStream->Available(&avail);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  char* temp = new char[avail];
+  if (!temp)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  rv = inputStream->Read(temp, avail, &read);
+  if (NS_SUCCEEDED(rv) && avail != read)
+    rv = NS_ERROR_UNEXPECTED;
+
+  if (NS_FAILED(rv)) {
+    delete temp;
+    return rv;
+  }
+
+  *len = avail;
+  *buffer = temp;
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/startupcache/test/Makefile.in
@@ -0,0 +1,53 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla.org.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     Benedict Hsieh <bhsieh@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE		= test_startupcache
+
+CPP_UNIT_TESTS = TestStartupCache.cpp
+
+EXTRA_DSO_LIBS += xul
+LIBS += $(MOZ_COMPONENT_LIBS)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/startupcache/test/TestStartupCache.cpp
@@ -0,0 +1,332 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Startup Cache.
+ *
+ * The Initial Developer of the Original Code is
+ * The Mozilla Foundation <http://www.mozilla.org/>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Benedict Hsieh <bhsieh@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "TestHarness.h"
+
+#include "nsThreadUtils.h"
+#include "nsIClassInfo.h"
+#include "nsIOutputStream.h"
+#include "nsIObserver.h"
+#include "nsISerializable.h"
+#include "nsISupports.h"
+#include "nsIStartupCache.h"
+#include "nsIStringStream.h"
+#include "nsIStorageStream.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIURI.h"
+#include "nsStringAPI.h"
+
+namespace mozilla {
+namespace scache {
+
+NS_IMPORT nsresult
+NS_NewObjectInputStreamFromBuffer(char* buffer, PRUint32 len, 
+                                  nsIObjectInputStream** stream);
+
+// We can't retrieve the wrapped stream from the objectOutputStream later,
+// so we return it here.
+NS_IMPORT nsresult
+NS_NewObjectOutputWrappedStorageStream(nsIObjectOutputStream **wrapperStream,
+                                       nsIStorageStream** stream);
+
+NS_IMPORT nsresult
+NS_NewBufferFromStorageStream(nsIStorageStream *storageStream, 
+                              char** buffer, PRUint32* len);
+}
+}
+
+using namespace mozilla::scache;
+
+#define NS_ENSURE_STR_MATCH(str1, str2, testname)  \
+PR_BEGIN_MACRO                                     \
+if (0 != strcmp(str1, str2)) {                     \
+  fail("failed " testname);                        \
+  return NS_ERROR_FAILURE;                         \
+}                                                  \
+passed("passed " testname);                        \
+PR_END_MACRO
+
+nsresult
+WaitForStartupTimer() {
+  nsresult rv;
+  nsCOMPtr<nsIStartupCache> sc 
+    = do_GetService("@mozilla.org/startupcache/cache;1");
+  PR_Sleep(10 * PR_TicksPerSecond());
+  
+  PRBool complete;
+  while (true) {
+    NS_ProcessPendingEvents(nsnull);
+    rv = sc->StartupWriteComplete(&complete);
+    if (NS_FAILED(rv) || complete)
+      break;
+    PR_Sleep(1 * PR_TicksPerSecond());
+  }
+  return rv;
+}
+
+nsresult
+TestStartupWriteRead() {
+  nsresult rv;
+  nsCOMPtr<nsIStartupCache> sc 
+    = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+  if (!sc) {
+    fail("didn't get a pointer...");
+    return NS_ERROR_FAILURE;
+  } else {
+    passed("got a pointer?");
+  }
+  sc->InvalidateCache();
+  
+  char* buf = "Market opportunities for BeardBook";
+  char* id = "id";
+  char* outbufPtr = NULL;
+  nsAutoArrayPtr<char> outbuf;  
+  PRUint32 len;
+  
+  rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = sc->GetBuffer(id, &outbufPtr, &len);
+  NS_ENSURE_SUCCESS(rv, rv);
+  outbuf = outbufPtr;
+  NS_ENSURE_STR_MATCH(buf, outbuf, "pre-write read");
+
+  rv = WaitForStartupTimer();
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = sc->GetBuffer(id, &outbufPtr, &len);
+  NS_ENSURE_SUCCESS(rv, rv);
+  outbuf = outbufPtr;
+  NS_ENSURE_STR_MATCH(buf, outbuf, "simple write/read");
+
+  return NS_OK;
+}
+
+nsresult
+TestWriteInvalidateRead() {
+  nsresult rv;
+  char* buf = "BeardBook competitive analysis";
+  char* id = "id";
+  char* outbuf = NULL;
+  PRUint32 len;
+  nsCOMPtr<nsIStartupCache> sc 
+    = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+  sc->InvalidateCache();
+
+  rv = sc->PutBuffer(id, buf, strlen(buf) + 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  sc->InvalidateCache();
+
+  rv = sc->GetBuffer(id, &outbuf, &len);
+  delete[] outbuf;
+  if (rv == NS_ERROR_NOT_AVAILABLE) {
+    passed("buffer not available after invalidate");
+  } else if (NS_SUCCEEDED(rv)) {
+    fail("GetBuffer succeeded unexpectedly after invalidate");
+    return NS_ERROR_UNEXPECTED;
+  } else {
+    fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
+    return rv;
+  }
+
+  sc->InvalidateCache();
+  return NS_OK;
+}
+
+nsresult
+TestWriteObject() {
+  nsresult rv;
+
+  nsCOMPtr<nsIURI> obj
+    = do_CreateInstance("@mozilla.org/network/simple-uri;1");
+  if (!obj) {
+    fail("did not create object in test write object");
+    return NS_ERROR_UNEXPECTED;
+  }
+  NS_NAMED_LITERAL_CSTRING(spec, "http://www.mozilla.org");
+  obj->SetSpec(spec);
+  nsCOMPtr<nsIStartupCache> sc = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+
+  sc->InvalidateCache();
+  
+  // Create an object stream. Usually this is done with
+  // NS_NewObjectOutputWrappedStorageStream, but that uses
+  // StartupCache::GetSingleton in debug builds, and we
+  // don't have access to that here. Obviously.
+  char* id = "id";
+  nsCOMPtr<nsIStorageStream> storageStream
+    = do_CreateInstance("@mozilla.org/storagestream;1");
+  NS_ENSURE_ARG_POINTER(storageStream);
+  
+  rv = storageStream->Init(256, (PRUint32) -1, nsnull);
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  nsCOMPtr<nsIObjectOutputStream> objectOutput
+    = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+  if (!objectOutput)
+    return NS_ERROR_OUT_OF_MEMORY;
+  
+  nsCOMPtr<nsIOutputStream> outputStream
+    = do_QueryInterface(storageStream);
+  
+  rv = objectOutput->SetOutputStream(outputStream);
+
+  if (NS_FAILED(rv)) {
+    fail("failed to create output stream");
+    return rv;
+  }
+  nsCOMPtr<nsISupports> objQI(do_QueryInterface(obj));
+  rv = objectOutput->WriteObject(objQI, PR_TRUE);
+  if (NS_FAILED(rv)) {
+    fail("failed to write object");
+    return rv;
+  }
+
+  char* bufPtr = NULL;
+  nsAutoArrayPtr<char> buf;
+  PRUint32 len;
+  NS_NewBufferFromStorageStream(storageStream, &bufPtr, &len);
+  buf = bufPtr;
+
+  // Since this is a post-startup write, it should be written and
+  // available.
+  rv = sc->PutBuffer(id, buf, len);
+  if (NS_FAILED(rv)) {
+    fail("failed to insert input stream");
+    return rv;
+  }
+    
+  char* buf2Ptr = NULL;
+  nsAutoArrayPtr<char> buf2;
+  PRUint32 len2;
+  nsCOMPtr<nsIObjectInputStream> objectInput;
+  rv = sc->GetBuffer(id, &buf2Ptr, &len2);
+  if (NS_FAILED(rv)) {
+    fail("failed to retrieve buffer");
+    return rv;
+  }
+  buf2 = buf2Ptr;
+
+  rv = NS_NewObjectInputStreamFromBuffer(buf2, len2, getter_AddRefs(objectInput));
+  if (NS_FAILED(rv)) {
+    fail("failed to created input stream");
+    return rv;
+  }  
+  buf2.forget();
+
+  nsCOMPtr<nsISupports> deserialized;
+  rv = objectInput->ReadObject(PR_TRUE, getter_AddRefs(deserialized));
+  if (NS_FAILED(rv)) {
+    fail("failed to read object");
+    return rv;
+  }
+  
+  PRBool match = false;
+  nsCOMPtr<nsIURI> uri(do_QueryInterface(deserialized));
+  if (uri) {
+    nsCString outSpec;
+    uri->GetSpec(outSpec);
+    match = outSpec.Equals(spec);
+  }
+  if (!match) {
+    fail("deserialized object has incorrect information");
+    return rv;
+  }
+  
+  passed("write object");
+  return NS_OK;
+}
+
+nsresult
+TestEarlyShutdown() {
+  nsresult rv;
+  nsCOMPtr<nsIStartupCache> sc 
+    = do_GetService("@mozilla.org/startupcache/cache;1", &rv);
+  sc->InvalidateCache();
+
+  char* buf = "Find your soul beardmate on BeardBook";
+  char* id = "id";
+  PRUint32 len;
+  char* outbuf = NULL;
+  
+  sc->ResetStartupWriteTimer();
+  rv = sc->PutBuffer(buf, id, strlen(buf) + 1);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIObserver> obs;
+  sc->GetObserver(getter_AddRefs(obs));
+  obs->Observe(nsnull, "xpcom-shutdown", nsnull);
+  rv = WaitForStartupTimer();
+  NS_ENSURE_SUCCESS(rv, rv);
+  
+  rv = sc->GetBuffer(id, &outbuf, &len);
+  delete[] outbuf;
+
+  if (rv == NS_ERROR_NOT_AVAILABLE) {
+    passed("buffer not available after early shutdown");
+  } else if (NS_SUCCEEDED(rv)) {
+    fail("GetBuffer succeeded unexpectedly after early shutdown");
+    return NS_ERROR_UNEXPECTED;
+  } else {
+    fail("GetBuffer gave an unexpected failure, expected NOT_AVAILABLE");
+    return rv;
+  }
+ 
+  return NS_OK;
+}
+
+
+int main(int argc, char** argv)
+{
+  int rv = 0;
+  nsresult rv2;
+  ScopedXPCOM xpcom("Startup Cache");
+  
+  if (NS_FAILED(TestStartupWriteRead()))
+    rv = 1;
+  if (NS_FAILED(TestWriteInvalidateRead()))
+    rv = 1;
+  if (NS_FAILED(TestWriteObject()))
+    rv = 1;
+  if (NS_FAILED(TestEarlyShutdown()))
+    rv = 1;
+  
+  return rv;
+}
--- a/toolkit/library/libxul-config.mk
+++ b/toolkit/library/libxul-config.mk
@@ -134,16 +134,17 @@ endif
 
 # component libraries
 COMPONENT_LIBS += \
 	necko \
 	uconv \
 	i18n \
 	chardet \
 	jar$(VERSION_NUMBER) \
+        startupcache \
 	pref \
 	htmlpars \
 	imglib2 \
 	gklayout \
 	docshell \
 	embedcomponents \
 	webbrwsr \
 	nsappshell \
--- a/toolkit/library/nsStaticXULComponents.cpp
+++ b/toolkit/library/nsStaticXULComponents.cpp
@@ -236,16 +236,17 @@
     MODULE(nsI18nModule)                     \
     MODULE(nsChardetModule)                  \
     UNIVERSALCHARDET_MODULE                  \
     MODULE(necko)                            \
     PERMISSIONS_MODULES                      \
     AUTH_MODULE                              \
     MODULE(nsJarModule)                      \
     ZIPWRITER_MODULE                         \
+    MODULE(StartupCacheModule)               \
     MODULE(nsPrefModule)                     \
     RDF_MODULES                              \
     MODULE(nsParserModule)                   \
     GFX_MODULES                              \
     WIDGET_MODULES                           \
     MODULE(nsImageLib2Module)                \
     ICON_MODULE                              \
     JETPACK_MODULES                          \
--- a/toolkit/toolkit-makefiles.sh
+++ b/toolkit/toolkit-makefiles.sh
@@ -774,16 +774,20 @@ MAKEFILES_extensions="
   extensions/cookie/Makefile
   extensions/permissions/Makefile
   extensions/pref/Makefile
   extensions/pref/autoconfig/Makefile
   extensions/pref/autoconfig/public/Makefile
   extensions/pref/autoconfig/src/Makefile
 "
 
+MAKEFILES_startupcache="
+  startupcache/Makefile
+"
+
 add_makefiles "
   $MAKEFILES_db
   $MAKEFILES_dom
   $MAKEFILES_editor
   $MAKEFILES_xmlparser
   $MAKEFILES_gfx
   $MAKEFILES_htmlparser
   $MAKEFILES_intl
@@ -815,16 +819,17 @@ add_makefiles "
   $MAKEFILES_embedding
   $MAKEFILES_xulapp
   $MAKEFILES_libpr0n
   $MAKEFILES_accessible
   $MAKEFILES_zlib
   $MAKEFILES_libmar
   $MAKEFILES_lib7z
   $MAKEFILES_extensions
+  $MAKEFILES_startupcache
 "
 
 #
 # Conditional makefiles
 #
 
 if [ "$ENABLE_TESTS" ]; then
   add_makefiles "
@@ -921,16 +926,17 @@ if [ "$ENABLE_TESTS" ]; then
     modules/libpref/test/Makefile
     modules/plugin/test/Makefile
     modules/plugin/test/mochitest/Makefile
     modules/plugin/test/testplugin/Makefile
     netwerk/test/httpserver/Makefile
     parser/htmlparser/tests/mochitest/Makefile
     parser/xml/test/Makefile
     rdf/tests/triplescat/Makefile
+    startupcache/tests/Makefile
     testing/mochitest/Makefile
     testing/mochitest/MochiKit/Makefile
     testing/mochitest/chrome/Makefile
     testing/mochitest/ssltunnel/Makefile
     testing/mochitest/static/Makefile
     testing/mochitest/tests/Makefile
     testing/mochitest/tests/MochiKit-1.4.2/Makefile
     testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Makefile
--- a/toolkit/toolkit-tiers.mk
+++ b/toolkit/toolkit-tiers.mk
@@ -235,16 +235,20 @@ tier_platform_dirs += extensions/java/xp
 endif
 
 ifndef BUILD_STATIC_LIBS
 ifneq (,$(MOZ_ENABLE_GTK2))
 tier_platform_dirs += embedding/browser/gtk
 endif
 endif
 
+ifdef MOZ_ENABLE_LIBXUL
+tier_platform_dirs += startupcache
+endif
+
 ifndef BUILD_STATIC_LIBS
 tier_platform_dirs += toolkit/library
 endif
 
 ifdef MOZ_ENABLE_LIBXUL
 tier_platform_dirs += xpcom/stub
 endif
 
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -2486,16 +2486,19 @@ static void RemoveComponentRegistries(ns
   if (!file)
     return;
 
   file->AppendNative(NS_LITERAL_CSTRING("XUL" PLATFORM_FASL_SUFFIX));
   file->Remove(PR_FALSE);
   
   file->SetNativeLeafName(NS_LITERAL_CSTRING("XPC" PLATFORM_FASL_SUFFIX));
   file->Remove(PR_FALSE);
+
+  file->SetNativeLeafName(NS_LITERAL_CSTRING("startupCache"));
+  file->Remove(PR_TRUE);
 }
 
 // To support application initiated restart via nsIAppStartup.quit, we
 // need to save various environment variables, and then restore them
 // before re-launching the application.
 
 static struct {
   const char *name;
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -142,16 +142,20 @@ extern nsresult nsStringInputStreamConst
 #include <locale.h>
 #include "mozilla/Services.h"
 #include "mozilla/FunctionTimer.h"
 #include "mozilla/Omnijar.h"
 
 #include "nsChromeRegistry.h"
 #include "nsChromeProtocolHandler.h"
 
+#ifdef MOZ_ENABLE_LIBXUL
+#include "mozilla/scache/StartupCache.h"
+#endif
+
 #ifdef MOZ_IPC
 #include "base/at_exit.h"
 #include "base/command_line.h"
 #include "base/message_loop.h"
 
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 
 using base::AtExitManager;
@@ -537,16 +541,19 @@ NS_InitXPCOM2(nsIServiceManager* *result
 
     NS_TIME_FUNCTION_MARK("Next: register category providers");
 
     // After autoreg, but before we actually instantiate any components,
     // add any services listed in the "xpcom-directory-providers" category
     // to the directory service.
     nsDirectoryService::gService->RegisterCategoryProviders();
 
+#ifdef MOZ_ENABLE_LIBXUL
+    mozilla::scache::StartupCache::GetSingleton();
+#endif
     NS_TIME_FUNCTION_MARK("Next: create services from category");
 
     // Notify observers of xpcom autoregistration start
     NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, 
                                   nsnull,
                                   NS_XPCOM_STARTUP_OBSERVER_ID);
     
     return NS_OK;
@@ -614,17 +621,19 @@ ShutdownXPCOM(nsIServiceManager* servMgr
             {
                 (void) observerService->
                     NotifyObservers(mgr, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
                                     nsnull);
             }
         }
 
         NS_ProcessPendingEvents(thread);
-
+#ifdef MOZ_ENABLE_LIBXUL
+        mozilla::scache::StartupCache::DeleteSingleton();
+#endif
         if (observerService)
             (void) observerService->
                 NotifyObservers(nsnull, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID,
                                 nsnull);
 
         NS_ProcessPendingEvents(thread);
 
         // Shutdown the timer thread and all timers that might still be alive before
--- a/xpcom/tests/TestHarness.h
+++ b/xpcom/tests/TestHarness.h
@@ -50,16 +50,17 @@
 #include "nsAutoPtr.h"
 #include "nsStringGlue.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsIDirectoryService.h"
 #include "nsIFile.h"
 #include "nsIProperties.h"
+#include "nsXULAppAPI.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
 
 static PRUint32 gFailCount = 0;
 
 /**
  * Prints the given failure message and arguments using printf, prepending
@@ -336,17 +337,18 @@ class ScopedXPCOM : public nsIDirectoryS
       if (mDirSvcProvider &&
           NS_SUCCEEDED(mDirSvcProvider->GetFile(aProperty, _persistent,
                                                 _result))) {
         return NS_OK;
       }
 
       // Otherwise, the test harness provides some directories automatically.
       if (0 == strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) ||
-          0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR)) {
+          0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR) ||
+          0 == strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) {
         nsCOMPtr<nsIFile> profD = GetProfileDirectory();
         NS_ENSURE_TRUE(profD, NS_ERROR_FAILURE);
 
         nsCOMPtr<nsIFile> clone;
         nsresult rv = profD->Clone(getter_AddRefs(clone));
         NS_ENSURE_SUCCESS(rv, rv);
 
         *_persistent = PR_TRUE;