bug 520309, startupcache core r=dwitte sr=bsmedberg a=bsmedberg
authorbhsieh@mozilla.com
Thu, 12 Aug 2010 12:37:44 -0700
changeset 51713 36e2a15feb6ae9ff9d76d7f6d09075445c47e41f
parent 51567 54a5ae891dba6ba156238b42825176deebf78a7f
child 51714 78e8c3514fff980a02664efcddc8f02132164b81
push idunknown
push userunknown
push dateunknown
reviewersdwitte, bsmedberg, bsmedberg
bugs520309
milestone2.0b5pre
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,628 @@
+/* -*-  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() 
+{
+  // Generally, the in-memory table should be empty here,
+  // but in special cases (like Talos Ts tests) we
+  // could shut down before we write.
+  // This mechanism will change when IO is moved off-thread
+  // (bug 586859) or when Talos first-run is changed to allow
+  // our timer to work (bug 591471).
+  WriteToDisk();
+  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 (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;