Bug 536544 - slow window.localStorage.setItem performance, r=sdwilsh, sr=jst, a=blocking2.0:final+
authorHonza Bambas <honzab.moz@firemni.cz>
Mon, 25 Oct 2010 18:47:23 +0200
changeset 56436 cdef53b45c9cc7bfba66b5b0b39912d9b23c63cf
parent 56435 760cbafe478b0c4e04307a5996631951d328f224
child 56437 c1f39b7c972eed3b7600a9f3c92745e19a87639c
push id16547
push userhonzab.moz@firemni.cz
push dateMon, 25 Oct 2010 16:48:17 +0000
treeherdermozilla-central@d9fdddb0a30e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssdwilsh, jst, blocking2.0
bugs536544
milestone2.0b8pre
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 536544 - slow window.localStorage.setItem performance, r=sdwilsh, sr=jst, a=blocking2.0:final+
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorage.h
dom/src/storage/nsDOMStorageDBWrapper.cpp
dom/src/storage/nsDOMStorageDBWrapper.h
dom/src/storage/nsDOMStoragePersistentDB.cpp
dom/src/storage/nsDOMStoragePersistentDB.h
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -71,16 +71,20 @@ static const PRUint32 ACCEPT_SESSION = 2
 static const PRUint32 BEHAVIOR_REJECT = 2;
 
 static const PRUint32 DEFAULT_QUOTA = 5 * 1024;
 // Be generous with offline apps by default...
 static const PRUint32 DEFAULT_OFFLINE_APP_QUOTA = 200 * 1024;
 // ... but warn if it goes over this amount
 static const PRUint32 DEFAULT_OFFLINE_WARN_QUOTA = 50 * 1024;
 
+// Intervals to flush the temporary table after in seconds
+#define NS_DOMSTORAGE_MAXIMUM_TEMPTABLE_INACTIVITY_TIME (5)
+#define NS_DOMSTORAGE_MAXIMUM_TEMPTABLE_AGE (30)
+
 static const char kPermissionType[] = "cookie";
 static const char kStorageEnabled[] = "dom.storage.enabled";
 static const char kDefaultQuota[] = "dom.storage.default_quota";
 static const char kCookiesBehavior[] = "network.cookie.cookieBehavior";
 static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
 static const char kOfflineAppWarnQuota[] = "offline-apps.quota.warn";
 static const char kOfflineAppQuota[] = "offline-apps.quota.max";
 
@@ -409,16 +413,20 @@ nsDOMStorageManager::Observe(nsISupports
 
 #ifdef MOZ_STORAGE
       nsresult rv = nsDOMStorage::InitDB();
       NS_ENSURE_SUCCESS(rv, rv);
 
       return nsDOMStorage::gStorageDB->DropSessionOnlyStoragesForHost(host);
 #endif
     }
+  } else if (!strcmp(aTopic, "timer-callback")) {
+    nsCOMPtr<nsIObserverService> obsserv = mozilla::services::GetObserverService();
+    if (obsserv)
+      obsserv->NotifyObservers(nsnull, NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER, nsnull);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMStorageManager::GetUsage(const nsAString& aDomain,
                               PRInt32 *aUsage)
@@ -528,16 +536,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 DOMCI_DATA(StorageObsolete, nsDOMStorage)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsDOMStorage, nsIDOMStorageObsolete)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsDOMStorage, nsIDOMStorageObsolete)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStorage)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMStorageObsolete)
   NS_INTERFACE_MAP_ENTRY(nsIDOMStorageObsolete)
   NS_INTERFACE_MAP_ENTRY(nsPIDOMStorage)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(StorageObsolete)
 NS_INTERFACE_MAP_END
 
 nsresult
 NS_NewDOMStorage(nsISupports* aOuter, REFNSIID aIID, void** aResult)
 {
   nsDOMStorage* storage = new nsDOMStorage();
   if (!storage)
@@ -558,16 +568,17 @@ NS_NewDOMStorage2(nsISupports* aOuter, R
 
 nsDOMStorage::nsDOMStorage()
   : mUseDB(PR_FALSE)
   , mSessionOnly(PR_TRUE)
   , mStorageType(nsPIDOMStorage::Unknown)
   , mItemsCached(PR_FALSE)
   , mEventBroadcaster(nsnull)
   , mCanUseChromePersist(false)
+  , mLoadedTemporaryTable(false)
 {
   mSecurityChecker = this;
   mItems.Init(8);
   if (nsDOMStorageManager::gStorageManager)
     nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
 }
 
 nsDOMStorage::nsDOMStorage(nsDOMStorage& aThat)
@@ -576,16 +587,19 @@ nsDOMStorage::nsDOMStorage(nsDOMStorage&
   , mSessionOnly(PR_TRUE)
   , mStorageType(aThat.mStorageType)
   , mItemsCached(PR_FALSE)
 #ifdef MOZ_STORAGE
   , mScopeDBKey(aThat.mScopeDBKey)
 #endif
   , mEventBroadcaster(nsnull)
   , mCanUseChromePersist(aThat.mCanUseChromePersist)
+  , mLoadedTemporaryTable(aThat.mLoadedTemporaryTable)
+  , mLastTemporaryTableAccessTime(aThat.mLastTemporaryTableAccessTime)
+  , mTemporaryTableAge(aThat.mTemporaryTableAge)
 {
   mSecurityChecker = this;
   mItems.Init(8);
 
   if (nsDOMStorageManager::gStorageManager)
     nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
 }
 
@@ -684,16 +698,18 @@ nsDOMStorage::InitAsLocalStorage(nsIPrin
 
   mStorageType = LocalStorage;
 
   nsCOMPtr<nsIURI> URI;
   if (NS_SUCCEEDED(aPrincipal->GetURI(getter_AddRefs(URI))) && URI) {
     mCanUseChromePersist = URICanUseChromePersist(URI);
   }
 
+  RegisterObservers();
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorage::InitAsGlobalStorage(const nsACString &aDomainDemanded)
 {
   mDomain = aDomainDemanded;
 #ifdef MOZ_STORAGE
@@ -709,16 +725,19 @@ nsDOMStorage::InitAsGlobalStorage(const 
   nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomainDemanded,
       PR_TRUE, PR_FALSE, mQuotaDomainDBKey);
   nsDOMStorageDBWrapper::CreateQuotaDomainDBKey(aDomainDemanded,
       PR_TRUE, PR_TRUE, mQuotaETLDplus1DomainDBKey);
 #endif
 
   mStorageType = GlobalStorage;
   mEventBroadcaster = this;
+
+  RegisterObservers();
+
   return NS_OK;
 }
 
 static PLDHashOperator
 CopyStorageItems(nsSessionStorageEntry* aEntry, void* userArg)
 {
   nsDOMStorage* newstorage = static_cast<nsDOMStorage*>(userArg);
 
@@ -1036,41 +1055,42 @@ NS_IMETHODIMP
 nsDOMStorage::SetItem(const nsAString& aKey, const nsAString& aData)
 {
   if (!CacheStoragePermissions())
     return NS_ERROR_DOM_SECURITY_ERR;
 
   if (aKey.IsEmpty())
     return NS_OK;
 
+  nsresult rv;
   nsString oldValue;
   SetDOMStringToNull(oldValue);
 
-  nsresult rv;
-  nsRefPtr<nsDOMStorageItem> newitem = nsnull;
+  // First store the value to the database, we need to do this before we update
+  // the mItems cache.  SetDBValue is using the old cached value to decide
+  // on quota checking.
+  bool isCallerSecure = IsCallerSecure();
+  if (UseDB()) {
+    rv = SetDBValue(aKey, aData, isCallerSecure);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
   if (entry) {
-    if (entry->mItem->IsSecure() && !IsCallerSecure()) {
+    if (entry->mItem->IsSecure() && !isCallerSecure) {
       return NS_ERROR_DOM_SECURITY_ERR;
     }
     oldValue = entry->mItem->GetValueInternal();
     entry->mItem->SetValueInternal(aData);
   }
   else {
-    newitem = new nsDOMStorageItem(this, aKey, aData, IsCallerSecure());
+    nsRefPtr<nsDOMStorageItem> newitem =
+      new nsDOMStorageItem(this, aKey, aData, isCallerSecure);
     if (!newitem)
       return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (UseDB()) {
-    rv = SetDBValue(aKey, aData, IsCallerSecure());
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  if (newitem) {
     entry = mItems.PutEntry(aKey);
     NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
     entry->mItem = newitem;
   }
 
   if ((oldValue != aData || mStorageType == GlobalStorage) && mEventBroadcaster)
     mEventBroadcaster->BroadcastChangeNotification(aKey, oldValue, aData);
 
@@ -1105,17 +1125,17 @@ NS_IMETHODIMP nsDOMStorage::RemoveItem(c
     NS_ENSURE_SUCCESS(rv, rv);
 
     oldValue = value;
 
     rv = gStorageDB->RemoveKey(this, aKey, !IsOfflineAllowed(mDomain),
                                aKey.Length() + value.Length());
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mItemsCached = PR_FALSE;
+    // Before bug 536544 got fixed we were dropping mItemsCached flag here
 #endif
   }
   else if (entry) {
     // clear string as StorageItems may be referencing this item
     oldValue = entry->mItem->GetValueInternal();
     entry->mItem->ClearValue();
   }
 
@@ -1228,16 +1248,33 @@ nsDOMStorage::CacheKeysFromDB()
     mItemsCached = PR_TRUE;
   }
 #endif
 
   return NS_OK;
 }
 
 nsresult
+nsDOMStorage::GetCachedValue(const nsAString& aKey, nsAString& aValue,
+                             PRBool* aSecure)
+{
+  aValue.Truncate();
+  *aSecure = PR_FALSE;
+
+  nsSessionStorageEntry *entry = mItems.GetEntry(aKey);
+  if (!entry)
+    return NS_ERROR_NOT_AVAILABLE;
+
+  aValue = entry->mItem->GetValueInternal();
+  *aSecure = entry->mItem->IsSecure();
+
+  return NS_OK;
+}
+
+nsresult
 nsDOMStorage::GetDBValue(const nsAString& aKey, nsAString& aValue,
                          PRBool* aSecure)
 {
   aValue.Truncate();
 
 #ifdef MOZ_STORAGE
   if (!UseDB())
     return NS_OK;
@@ -1284,17 +1321,17 @@ nsDOMStorage::SetDBValue(const nsAString
                                   CanUseChromePersist());
 
   PRInt32 usage;
   rv = gStorageDB->SetKey(this, aKey, aValue, aSecure, quota,
                           !IS_PERMISSION_ALLOWED(offlineAppPermission),
                           &usage);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mItemsCached = PR_FALSE;
+  // Before bug 536544 got fixed we were dropping mItemsCached flag here
 
   if (warnQuota >= 0 && usage > warnQuota) {
     // try to include the window that exceeded the warn quota
     nsCOMPtr<nsIDOMWindow> window;
     JSContext *cx;
     nsCOMPtr<nsIJSContextStack> stack =
       do_GetService("@mozilla.org/js/xpc/ContextStack;1");
     if (stack && NS_SUCCEEDED(stack->Peek(&cx)) && cx) {
@@ -1463,16 +1500,94 @@ nsDOMStorage::BroadcastChangeNotificatio
   // Fire off a notification that a storage object changed. If the
   // storage object is a session storage object, we don't pass a
   // domain, but if it's a global storage object we do.
   observerService->NotifyObservers((nsIDOMStorageObsolete *)this,
                                    "dom-storage-changed",
                                    NS_ConvertUTF8toUTF16(mDomain).get());
 }
 
+nsresult
+nsDOMStorage::MaybeCommitTemporaryTable(bool force)
+{
+#ifdef MOZ_STORAGE
+  if (!UseDB())
+    return NS_OK;
+
+  if (!mLoadedTemporaryTable)
+    return NS_OK;
+
+  // If we are not forced to flush (e.g. on shutdown) then don't flush if the
+  // last table access is less then 5 seconds ago or the table itself is not
+  // older then 30 secs
+  if (!force &&
+     ((TimeStamp::Now() - mLastTemporaryTableAccessTime).ToSeconds() < 
+       NS_DOMSTORAGE_MAXIMUM_TEMPTABLE_INACTIVITY_TIME) &&
+     ((TimeStamp::Now() - mTemporaryTableAge).ToSeconds() < 
+       NS_DOMSTORAGE_MAXIMUM_TEMPTABLE_AGE))
+    return NS_OK;
+
+  return gStorageDB->FlushAndDeleteTemporaryTableForStorage(this);
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMStorage::RegisterObservers()
+{
+  nsCOMPtr<nsIObserverService> obsserv = mozilla::services::GetObserverService();
+  if (obsserv) {
+    obsserv->AddObserver(this, "profile-before-change", PR_TRUE);
+    obsserv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_TRUE);
+    obsserv->AddObserver(this, NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER, PR_TRUE);
+  }
+  return NS_OK;
+}
+
+bool
+nsDOMStorage::WasTemporaryTableLoaded()
+{
+  return mLoadedTemporaryTable;
+}
+
+void
+nsDOMStorage::SetTemporaryTableLoaded(bool loaded)
+{
+  if (loaded) {
+    mLastTemporaryTableAccessTime = TimeStamp::Now();
+    if (!mLoadedTemporaryTable)
+      mTemporaryTableAge = mLastTemporaryTableAccessTime;
+  }
+
+  mLoadedTemporaryTable = loaded;
+}
+
+NS_IMETHODIMP
+nsDOMStorage::Observe(nsISupports *subject,
+                      const char *topic,
+                      const PRUnichar *data)
+{
+  bool isProfileBeforeChange = !strcmp(topic, "profile-before-change");
+  bool isXPCOMShutdown = !strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+  bool isFlushTimer = !strcmp(topic, NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER);
+
+  if (isXPCOMShutdown || isProfileBeforeChange || isFlushTimer) {
+    nsresult rv = MaybeCommitTemporaryTable(isXPCOMShutdown || isProfileBeforeChange);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("DOMStorage: temporary table commit failed");
+    }
+
+    return NS_OK;
+  }
+
+  NS_WARNING("Unrecognized topic in nsDOMStorage::Observe");
+  return NS_OK;
+}
+
 //
 // nsDOMStorage2
 //
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMStorage2)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMStorage2)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mStorage)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@@ -2126,9 +2241,8 @@ nsDOMStorageEventObsolete::InitStorageEv
 {
   nsresult rv = InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mDomain = aDomainArg;
 
   return NS_OK;
 }
-
--- a/dom/src/storage/nsDOMStorage.h
+++ b/dom/src/storage/nsDOMStorage.h
@@ -54,29 +54,37 @@
 #include "nsPIDOMStorage.h"
 #include "nsIDOMToString.h"
 #include "nsDOMEvent.h"
 #include "nsIDOMStorageEvent.h"
 #include "nsIDOMStorageEventObsolete.h"
 #include "nsIDOMStorageManager.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+#include "mozilla/TimeStamp.h"
+
+#define NS_DOMSTORAGE_FLUSH_TIMER_OBSERVER "domstorage-flush-timer"
 
 #ifdef MOZ_STORAGE
 #include "nsDOMStorageDBWrapper.h"
 #endif
 
 #define IS_PERMISSION_ALLOWED(perm) \
       ((perm) != nsIPermissionManager::UNKNOWN_ACTION && \
       (perm) != nsIPermissionManager::DENY_ACTION)
 
 class nsDOMStorage;
 class nsIDOMStorage;
 class nsDOMStorageItem;
 
+using mozilla::TimeStamp;
+using mozilla::TimeDuration;
+
 class nsDOMStorageEntry : public nsVoidPtrHashKey
 {
 public:
   nsDOMStorageEntry(KeyTypePointer aStr);
   nsDOMStorageEntry(const nsDOMStorageEntry& aToCopy);
   ~nsDOMStorageEntry();
 
   // weak reference so that it can be deleted when no longer used
@@ -123,29 +131,30 @@ public:
 
 protected:
 
   nsTHashtable<nsDOMStorageEntry> mStorages;
   PRBool mInPrivateBrowsing;
 };
 
 class nsDOMStorage : public nsIDOMStorageObsolete,
-                     public nsPIDOMStorage
+                     public nsPIDOMStorage,
+                     public nsIObserver,
+                     public nsSupportsWeakReference
 {
 public:
   nsDOMStorage();
   nsDOMStorage(nsDOMStorage& aThat);
   virtual ~nsDOMStorage();
 
-  // nsISupports
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMStorage, nsIDOMStorageObsolete)
 
-  // nsIDOMStorageObsolete
   NS_DECL_NSIDOMSTORAGEOBSOLETE
+  NS_DECL_NSIOBSERVER
 
   // Helpers for implementing nsIDOMStorage
   nsresult GetItem(const nsAString& key, nsAString& aData);
   nsresult Clear();
 
   // nsPIDOMStorage
   virtual nsresult InitAsSessionStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI);
   virtual nsresult InitAsLocalStorage(nsIPrincipal *aPrincipal, const nsSubstring &aDocumentURI);
@@ -190,16 +199,23 @@ public:
   static PRBool
   URICanUseChromePersist(nsIURI* aURI);
   
   // Check whether storage may be used.  Updates mSessionOnly based on
   // the result of CanUseStorage.
   PRBool
   CacheStoragePermissions();
 
+  // retrieve the value and secure state corresponding to a key out of storage
+  // that has been cached in mItems hash table.
+  nsresult
+  GetCachedValue(const nsAString& aKey,
+                 nsAString& aValue,
+                 PRBool* aSecure);
+
   // retrieve the value and secure state corresponding to a key out of storage.
   nsresult
   GetDBValue(const nsAString& aKey,
              nsAString& aValue,
              PRBool* aSecure);
 
   // set the value corresponding to a key in the storage. If
   // aSecure is false, then attempts to modify a secure value
@@ -221,20 +237,27 @@ public:
 
   nsIDOMStorageItem* GetNamedItem(const nsAString& aKey, nsresult* aResult);
 
   static nsDOMStorage* FromSupports(nsISupports* aSupports)
   {
     return static_cast<nsDOMStorage*>(static_cast<nsIDOMStorageObsolete*>(aSupports));
   }
 
+  nsresult RegisterObservers();
+  nsresult MaybeCommitTemporaryTable(bool force);
+
+  bool WasTemporaryTableLoaded();
+  void SetTemporaryTableLoaded(bool loaded);
+
 protected:
 
   friend class nsDOMStorageManager;
   friend class nsDOMStorage2;
+  friend class nsDOMStoragePersistentDB;
 
   static nsresult InitDB();
 
   // cache the keys from the database for faster lookup
   nsresult CacheKeysFromDB();
 
   PRBool CanAccessSystem(nsIPrincipal *aPrincipal);
 
@@ -273,16 +296,20 @@ protected:
   nsCString mQuotaDomainDBKey;
 
   friend class nsIDOMStorage2;
   nsPIDOMStorage* mSecurityChecker;
   nsPIDOMStorage* mEventBroadcaster;
 
   bool mCanUseChromePersist;
 
+  bool mLoadedTemporaryTable;
+  TimeStamp mLastTemporaryTableAccessTime;
+  TimeStamp mTemporaryTableAge;
+
 public:
   // e.g. "moc.rab.oof.:" or "moc.rab.oof.:http:80" depending
   // on association with a domain (globalStorage) or
   // an origin (localStorage).
   nsCString& GetScopeDBKey() {return mScopeDBKey;}
 
   // e.g. "moc.rab.%" - reversed eTLD+1 subpart of the domain or
   // reversed offline application allowed domain.
--- a/dom/src/storage/nsDOMStorageDBWrapper.cpp
+++ b/dom/src/storage/nsDOMStorageDBWrapper.cpp
@@ -64,16 +64,27 @@ void ReverseString(const nsCSubstring& s
   result.EndWriting(destEnd);
 
   while (sourceBegin != sourceEnd) {
     *(--destEnd) = *sourceBegin;
     ++sourceBegin;
   }
 }
 
+nsDOMStorageDBWrapper::nsDOMStorageDBWrapper()
+{
+}
+
+nsDOMStorageDBWrapper::~nsDOMStorageDBWrapper()
+{
+  if (mFlushTimer) {
+    mFlushTimer->Cancel();
+  }
+}
+
 nsresult
 nsDOMStorageDBWrapper::Init()
 {
   nsresult rv;
 
   rv = mPersistentDB.Init(NS_LITERAL_STRING("webappsstore.sqlite"));
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -81,20 +92,53 @@ nsDOMStorageDBWrapper::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mSessionOnlyDB.Init(&mPersistentDB);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mPrivateBrowsingDB.Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mFlushTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mFlushTimer->Init(nsDOMStorageManager::gStorageManager, 5000,
+                         nsITimer::TYPE_REPEATING_SLACK);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 nsresult
+nsDOMStorageDBWrapper::EnsureLoadTemporaryTableForStorage(nsDOMStorage* aStorage)
+{
+  if (aStorage->CanUseChromePersist())
+    return mChromePersistentDB.EnsureLoadTemporaryTableForStorage(aStorage);
+  if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
+    return NS_OK;
+  if (aStorage->SessionOnly())
+    return NS_OK;
+
+  return mPersistentDB.EnsureLoadTemporaryTableForStorage(aStorage);
+}
+
+nsresult
+nsDOMStorageDBWrapper::FlushAndDeleteTemporaryTableForStorage(nsDOMStorage* aStorage)
+{
+  if (aStorage->CanUseChromePersist())
+    return mChromePersistentDB.FlushAndDeleteTemporaryTableForStorage(aStorage);
+  if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
+    return NS_OK;
+  if (aStorage->SessionOnly())
+    return NS_OK;
+
+  return mPersistentDB.FlushAndDeleteTemporaryTableForStorage(aStorage);
+}
+
+nsresult
 nsDOMStorageDBWrapper::GetAllKeys(nsDOMStorage* aStorage,
                                   nsTHashtable<nsSessionStorageEntry>* aKeys)
 {
   if (aStorage->CanUseChromePersist())
     return mChromePersistentDB.GetAllKeys(aStorage, aKeys);
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())
     return mPrivateBrowsingDB.GetAllKeys(aStorage, aKeys);
   if (aStorage->SessionOnly())
--- a/dom/src/storage/nsDOMStorageDBWrapper.h
+++ b/dom/src/storage/nsDOMStorageDBWrapper.h
@@ -81,22 +81,27 @@ class nsSessionStorageEntry;
  *
  * When calculating quotas, we want to lump together everything in an ETLD+1.
  * So we use a "quota key" during lookups to calculate the quota.  So the
  * quota key for localStorage at http://foo.bar.com is "moc.rab.". */
 
 class nsDOMStorageDBWrapper
 {
 public:
-  nsDOMStorageDBWrapper() {}
-  ~nsDOMStorageDBWrapper() {}
+  nsDOMStorageDBWrapper();
+  ~nsDOMStorageDBWrapper();
 
   nsresult
   Init();
 
+  nsresult
+  EnsureLoadTemporaryTableForStorage(nsDOMStorage* aStorage);
+  nsresult
+  FlushAndDeleteTemporaryTableForStorage(nsDOMStorage* aStorage);
+
   /**
    * Retrieve a list of all the keys associated with a particular domain.
    */
   nsresult
   GetAllKeys(nsDOMStorage* aStorage,
              nsTHashtable<nsSessionStorageEntry>* aKeys);
 
   /**
@@ -216,11 +221,13 @@ public:
   static nsresult GetDomainFromScopeKey(const nsACString& aScope,
                                          nsACString& aDomain);
 
 protected:
   nsDOMStoragePersistentDB mChromePersistentDB;
   nsDOMStoragePersistentDB mPersistentDB;
   nsDOMStorageMemoryDB mSessionOnlyDB;
   nsDOMStorageMemoryDB mPrivateBrowsingDB;
+
+  nsCOMPtr<nsITimer> mFlushTimer;
 };
 
 #endif /* nsDOMStorageDB_h___ */
--- a/dom/src/storage/nsDOMStoragePersistentDB.cpp
+++ b/dom/src/storage/nsDOMStoragePersistentDB.cpp
@@ -43,16 +43,18 @@
 #include "nsDOMStoragePersistentDB.h"
 #include "nsIFile.h"
 #include "nsIVariant.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "mozStorageCID.h"
 #include "mozStorageHelper.h"
 #include "mozIStorageService.h"
+#include "mozIStorageBindingParamsArray.h"
+#include "mozIStorageBindingParams.h"
 #include "mozIStorageValueArray.h"
 #include "mozIStorageFunction.h"
 #include "nsPrintfCString.h"
 #include "nsNetUtil.h"
 
 class nsReverseStringSQLFunction : public mozIStorageFunction
 {
   NS_DECL_ISUPPORTS
@@ -89,16 +91,20 @@ nsReverseStringSQLFunction::OnFunctionCa
 class nsIsOfflineSQLFunction : public mozIStorageFunction
 {
   NS_DECL_ISUPPORTS
   NS_DECL_MOZISTORAGEFUNCTION
 };
 
 NS_IMPL_ISUPPORTS1(nsIsOfflineSQLFunction, mozIStorageFunction)
 
+nsDOMStoragePersistentDB::nsDOMStoragePersistentDB()
+{
+}
+
 NS_IMETHODIMP
 nsIsOfflineSQLFunction::OnFunctionCall(
     mozIStorageValueArray *aFunctionArguments, nsIVariant **aResult)
 {
   nsresult rv;
 
   nsCAutoString scope;
   rv = aFunctionArguments->GetUTF8String(0, scope);
@@ -118,16 +124,63 @@ nsIsOfflineSQLFunction::OnFunctionCall(
   rv = outVar->SetAsBool(hasOfflinePermission);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aResult = outVar.get();
   outVar.forget();
   return NS_OK;
 }
 
+class Binder 
+{
+public:
+  Binder(mozIStorageStatement* statement, nsresult *rv);
+
+  mozIStorageBindingParams* operator->();
+  nsresult Add();
+
+private:
+  mozIStorageStatement* mStmt;
+  nsCOMPtr<mozIStorageBindingParamsArray> mArray;
+  nsCOMPtr<mozIStorageBindingParams> mParams;
+};
+
+Binder::Binder(mozIStorageStatement* statement, nsresult *rv)
+ : mStmt(statement) 
+{
+  *rv = mStmt->NewBindingParamsArray(getter_AddRefs(mArray));
+  if (NS_FAILED(*rv))
+    return;
+
+  *rv = mArray->NewBindingParams(getter_AddRefs(mParams));
+  if (NS_FAILED(*rv))
+    return;
+
+  *rv = NS_OK;
+}
+
+mozIStorageBindingParams*
+Binder::operator->()
+{
+  return mParams;
+}
+
+nsresult
+Binder::Add()
+{
+  nsresult rv;
+
+  rv = mArray->AddParams(mParams);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mStmt->BindParameters(mArray);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return rv;
+}
+
 nsresult
 nsDOMStoragePersistentDB::Init(const nsString& aDatabaseName)
 {
   nsresult rv;
 
   nsCOMPtr<nsIFile> storageFile;
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                               getter_AddRefs(storageFile));
@@ -143,31 +196,74 @@ nsDOMStoragePersistentDB::Init(const nsS
   if (rv == NS_ERROR_FILE_CORRUPTED) {
     // delete the db and try opening again
     rv = storageFile->Remove(PR_FALSE);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = service->OpenDatabase(storageFile, getter_AddRefs(mConnection));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "PRAGMA temp_store = MEMORY"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozStorageTransaction transaction(mConnection, PR_FALSE);
+
   // Ensure Gecko 1.9.1 storage table
-  rv = mConnection->ExecuteSimpleSQL(
-         NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS webappsstore2 ("
-                            "scope TEXT, "
-                            "key TEXT, "
-                            "value TEXT, "
-                            "secure INTEGER, "
-                            "owner TEXT)"));
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+         "CREATE TABLE IF NOT EXISTS webappsstore2 ("
+         "scope TEXT, "
+         "key TEXT, "
+         "value TEXT, "
+         "secure INTEGER, "
+         "owner TEXT)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
         "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
         " ON webappsstore2(scope, key)"));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+         "CREATE TEMPORARY TABLE webappsstore2_temp ("
+         "scope TEXT, "
+         "key TEXT, "
+         "value TEXT, "
+         "secure INTEGER, "
+         "owner TEXT)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "CREATE UNIQUE INDEX scope_key_index_temp"
+        " ON webappsstore2_temp(scope, key)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "CREATE TEMPORARY VIEW webappsstore2_view AS "
+        "SELECT * FROM webappsstore2_temp "
+        "UNION ALL "
+        "SELECT * FROM webappsstore2 "
+          "WHERE NOT EXISTS ("
+            "SELECT scope, key FROM webappsstore2_temp "
+            "WHERE scope = webappsstore2.scope AND key = webappsstore2.key)"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // carry deletion to both the temporary table and the disk table
+  rv = mConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        "CREATE TEMPORARY TRIGGER webappsstore2_view_delete_trigger "
+        "INSTEAD OF DELETE ON webappsstore2_view "
+        "BEGIN "
+          "DELETE FROM webappsstore2_temp "
+          "WHERE scope = OLD.scope AND key = OLD.key; "
+          "DELETE FROM webappsstore2 "
+          "WHERE scope = OLD.scope AND key = OLD.key; "
+        "END"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
   NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
 
   rv = mConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<mozIStorageFunction> function2(new nsIsOfflineSQLFunction());
   NS_ENSURE_TRUE(function2, NS_ERROR_OUT_OF_MEMORY);
@@ -213,151 +309,311 @@ nsDOMStoragePersistentDB::Init(const nsS
                                 "FROM moz_webappsstore"));
       NS_ENSURE_SUCCESS(rv, rv);
 
       rv = mConnection->ExecuteSimpleSQL(
              NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
       NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // temporary - disk synchronization statements
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "INSERT INTO webappsstore2_temp"
+         " SELECT * FROM webappsstore2"
+         " WHERE scope = :scope AND NOT EXISTS ("
+            "SELECT scope, key FROM webappsstore2_temp "
+            "WHERE scope = webappsstore2.scope AND key = webappsstore2.key)"),
+         getter_AddRefs(mCopyToTempTableStatement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "INSERT OR REPLACE INTO webappsstore2"
+         " SELECT * FROM webappsstore2_temp"
+         " WHERE scope = :scope;"),
+         getter_AddRefs(mCopyBackToDiskStatement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "DELETE FROM webappsstore2_temp"
+         " WHERE scope = :scope;"),
+         getter_AddRefs(mDeleteTemporaryTableStatement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // retrieve all keys associated with a domain
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("SELECT key, secure FROM webappsstore2 "
-                            "WHERE scope = ?1"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "SELECT key, value, secure FROM webappsstore2_temp "
+         "WHERE scope = :scope"),
          getter_AddRefs(mGetAllKeysStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // retrieve a value given a domain and a key
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("SELECT value, secure FROM webappsstore2 "
-                            "WHERE scope = ?1 "
-                            "AND key = ?2"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "SELECT value, secure FROM webappsstore2_temp "
+         "WHERE scope = :scope "
+         "AND key = :key"),
          getter_AddRefs(mGetKeyValueStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // insert a new key
-  rv = mConnection->CreateStatement(
-    NS_LITERAL_CSTRING("INSERT OR REPLACE INTO "
-                            "webappsstore2(scope, key, value, secure) "
-                            "VALUES (?1, ?2, ?3, ?4)"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "INSERT OR REPLACE INTO "
+         "webappsstore2_temp(scope, key, value, secure) "
+         "VALUES (:scope, :key, :value, :secure)"),
          getter_AddRefs(mInsertKeyStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // update an existing key
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("UPDATE webappsstore2 "
-                            "SET value = ?1, secure = ?2"
-                            "WHERE scope = ?3 "
-                            "AND key = ?4"),
-         getter_AddRefs(mUpdateKeyStatement));
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // update the secure status of an existing key
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("UPDATE webappsstore2 "
-                            "SET secure = ?1 "
-                            "WHERE scope = ?2 "
-                            "AND key = ?3 "),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "UPDATE webappsstore2_temp "
+         "SET secure = :secure "
+         "WHERE scope = :scope "
+         "AND key = :key "),
          getter_AddRefs(mSetSecureStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // remove a key
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
-                            "WHERE scope = ?1 "
-                            "AND key = ?2"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "DELETE FROM webappsstore2_view "
+         "WHERE scope = :scope "
+         "AND key = :key"),
          getter_AddRefs(mRemoveKeyStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // remove keys owned by a specific domain
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
-                            "WHERE scope GLOB ?1"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "DELETE FROM webappsstore2_view "
+         "WHERE scope GLOB :scope"),
          getter_AddRefs(mRemoveOwnerStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // remove keys belonging exactly only to a specific domain
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("DELETE FROM webappsstore2 "
-                            "WHERE scope = ?1"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "DELETE FROM webappsstore2_view "
+         "WHERE scope = :scope"),
          getter_AddRefs(mRemoveStorageStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // remove all keys
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("DELETE FROM webappsstore2"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "DELETE FROM webappsstore2_view"),
          getter_AddRefs(mRemoveAllStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // check the usage for a given owner that is an offline-app allowed domain
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("SELECT SUM(LENGTH(key) + LENGTH(value)) "
-                            "FROM webappsstore2 "
-                            "WHERE scope GLOB ?1"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "SELECT SUM(LENGTH(key) + LENGTH(value)) "
+         "FROM ("
+           "SELECT key,value FROM webappsstore2_temp "
+           "WHERE scope GLOB :scope "
+           "UNION ALL "
+           "SELECT key,value FROM webappsstore2 "
+           "WHERE scope GLOB :scope "
+           "AND NOT EXISTS ("
+             "SELECT scope, key "
+             "FROM webappsstore2_temp "
+             "WHERE scope = webappsstore2.scope "
+             "AND key = webappsstore2.key"
+           ")"
+         ")"),
          getter_AddRefs(mGetFullUsageStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // check the usage for a given owner that is not an offline-app allowed domain
-  rv = mConnection->CreateStatement(
-         NS_LITERAL_CSTRING("SELECT SUM(LENGTH(key) + LENGTH(value)) "
-                            "FROM webappsstore2 "
-                            "WHERE scope GLOB ?1 "
-                            "AND NOT ISOFFLINE(scope)"),
+  rv = mConnection->CreateStatement(NS_LITERAL_CSTRING(
+         "SELECT SUM(LENGTH(key) + LENGTH(value)) "
+         "FROM ("
+           "SELECT key, value FROM webappsstore2_temp "
+           "WHERE scope GLOB :scope "
+           "AND NOT ISOFFLINE(scope) "
+           "UNION ALL "
+           "SELECT key, value FROM webappsstore2 "
+           "WHERE scope GLOB :scope "
+           "AND NOT ISOFFLINE(scope) "
+           "AND NOT EXISTS ("
+             "SELECT scope, key "
+             "FROM webappsstore2_temp "
+             "WHERE scope = webappsstore2.scope "
+             "AND key = webappsstore2.key"
+           ")"
+         ")"),
          getter_AddRefs(mGetOfflineExcludedUsageStatement));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = transaction.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMStoragePersistentDB::EnsureLoadTemporaryTableForStorage(nsDOMStorage* aStorage)
+{
+  if (!aStorage->WasTemporaryTableLoaded()) {
+    nsresult rv;
+
+    rv = MaybeCommitInsertTransaction();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mozStorageStatementScoper scope(mCopyToTempTableStatement);
+
+    Binder binder(mCopyToTempTableStatement, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), 
+                                      aStorage->GetScopeDBKey());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = binder.Add();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mCopyToTempTableStatement->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Always call this to update the last access time
+  aStorage->SetTemporaryTableLoaded(true);
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMStoragePersistentDB::FlushAndDeleteTemporaryTableForStorage(nsDOMStorage* aStorage)
+{
+  if (!aStorage->WasTemporaryTableLoaded())
+    return NS_OK;
+
+  mozStorageTransaction trans(mConnection, PR_FALSE);
+
+  nsresult rv;
+
+  {
+    mozStorageStatementScoper scope(mCopyBackToDiskStatement);
+
+    Binder binder(mCopyBackToDiskStatement, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                      aStorage->GetScopeDBKey());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = binder.Add();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mCopyBackToDiskStatement->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  {
+    mozStorageStatementScoper scope(mDeleteTemporaryTableStatement);
+
+    Binder binder(mDeleteTemporaryTableStatement, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                      aStorage->GetScopeDBKey());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = binder.Add();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mDeleteTemporaryTableStatement->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  rv = trans.Commit();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aStorage->SetTemporaryTableLoaded(false);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetAllKeys(nsDOMStorage* aStorage,
                                      nsTHashtable<nsSessionStorageEntry>* aKeys)
 {
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = EnsureLoadTemporaryTableForStorage(aStorage);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mGetAllKeysStatement);
 
-  nsresult rv = mGetAllKeysStatement->BindUTF8StringParameter(0, aStorage->GetScopeDBKey());
+  Binder binder(mGetAllKeysStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    aStorage->GetScopeDBKey());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool exists;
   while (NS_SUCCEEDED(rv = mGetAllKeysStatement->ExecuteStep(&exists)) &&
          exists) {
 
     nsAutoString key;
     rv = mGetAllKeysStatement->GetString(0, key);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    nsAutoString value;
+    rv = mGetAllKeysStatement->GetString(1, value);
+    NS_ENSURE_SUCCESS(rv, rv);
+
     PRInt32 secureInt = 0;
-    rv = mGetAllKeysStatement->GetInt32(1, &secureInt);
+    rv = mGetAllKeysStatement->GetInt32(2, &secureInt);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsSessionStorageEntry* entry = aKeys->PutEntry(key);
     NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
 
-    entry->mItem = new nsDOMStorageItem(aStorage, key, EmptyString(), secureInt);
+    entry->mItem = new nsDOMStorageItem(aStorage, key, value, secureInt);
     if (!entry->mItem) {
       aKeys->RawRemoveEntry(entry);
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetKeyValue(nsDOMStorage* aStorage,
                                       const nsAString& aKey,
                                       nsAString& aValue,
                                       PRBool* aSecure)
 {
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = EnsureLoadTemporaryTableForStorage(aStorage);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mGetKeyValueStatement);
 
-  nsresult rv = mGetKeyValueStatement->BindUTF8StringParameter(
-                                                  0, aStorage->GetScopeDBKey());
+  Binder binder(mGetKeyValueStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    aStorage->GetScopeDBKey());
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mGetKeyValueStatement->BindStringParameter(1, aKey);
+  rv = binder->BindStringByName(NS_LITERAL_CSTRING("key"),
+                                aKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool exists;
   rv = mGetKeyValueStatement->ExecuteStep(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt32 secureInt = 0;
   if (exists) {
@@ -380,93 +636,70 @@ nsresult
 nsDOMStoragePersistentDB::SetKey(nsDOMStorage* aStorage,
                                  const nsAString& aKey,
                                  const nsAString& aValue,
                                  PRBool aSecure,
                                  PRInt32 aQuota,
                                  PRBool aExcludeOfflineFromUsage,
                                  PRInt32 *aNewUsage)
 {
-  mozStorageStatementScoper scope(mGetKeyValueStatement);
+  nsresult rv;
+
+  rv = EnsureLoadTemporaryTableForStorage(aStorage);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt32 usage = 0;
-  nsresult rv;
   if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
     rv = GetUsage(aStorage, aExcludeOfflineFromUsage, &usage);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  aStorage->CacheKeysFromDB();
+
   usage += aKey.Length() + aValue.Length();
 
-  rv = mGetKeyValueStatement->BindUTF8StringParameter(0,
-                                                      aStorage->GetScopeDBKey());
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mGetKeyValueStatement->BindStringParameter(1, aKey);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoString previousValue;
+  PRBool secure;
+  rv = aStorage->GetCachedValue(aKey, previousValue, &secure);
+  if (NS_SUCCEEDED(rv)) {
+    if (!aSecure && secure)
+      return NS_ERROR_DOM_SECURITY_ERR;
+    usage -= aKey.Length() + previousValue.Length();
+  }
 
-  PRBool exists;
-  rv = mGetKeyValueStatement->ExecuteStep(&exists);
+  if (usage > aQuota) {
+    return NS_ERROR_DOM_QUOTA_REACHED;
+  }
+
+  rv = EnsureInsertTransaction();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (exists) {
-    if (!aSecure) {
-      PRInt32 secureInt = 0;
-      rv = mGetKeyValueStatement->GetInt32(1, &secureInt);
-      NS_ENSURE_SUCCESS(rv, rv);
-      if (secureInt)
-        return NS_ERROR_DOM_SECURITY_ERR;
-    }
+  mozStorageStatementScoper scopeinsert(mInsertKeyStatement);
 
-    nsAutoString previousValue;
-    rv = mGetKeyValueStatement->GetString(0, previousValue);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    usage -= aKey.Length() + previousValue.Length();
-
-    mGetKeyValueStatement->Reset();
-
-    if (usage > aQuota) {
-      return NS_ERROR_DOM_QUOTA_REACHED;
-    }
-
-    mozStorageStatementScoper scopeupdate(mUpdateKeyStatement);
+  Binder binder(mInsertKeyStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = mUpdateKeyStatement->BindStringParameter(0, aValue);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mUpdateKeyStatement->BindInt32Parameter(1, aSecure);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mUpdateKeyStatement->BindUTF8StringParameter(2,
-                                                      aStorage->GetScopeDBKey());
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mUpdateKeyStatement->BindStringParameter(3, aKey);
-    NS_ENSURE_SUCCESS(rv, rv);
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    aStorage->GetScopeDBKey());
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = binder->BindStringByName(NS_LITERAL_CSTRING("key"),
+                                aKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = binder->BindStringByName(NS_LITERAL_CSTRING("value"),
+                                aValue);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = binder->BindInt32ByName(NS_LITERAL_CSTRING("secure"),
+                               aSecure ? 1 : 0);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = mUpdateKeyStatement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-  else {
-    if (usage > aQuota) {
-      return NS_ERROR_DOM_QUOTA_REACHED;
-    }
-
-    mozStorageStatementScoper scopeinsert(mInsertKeyStatement);
+  rv = binder.Add();
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = mInsertKeyStatement->BindUTF8StringParameter(0,
-                                                      aStorage->GetScopeDBKey());
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mInsertKeyStatement->BindStringParameter(1, aKey);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mInsertKeyStatement->BindStringParameter(2, aValue);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = mInsertKeyStatement->BindInt32Parameter(3, aSecure);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = mInsertKeyStatement->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  rv = mInsertKeyStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
 
   if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
     mCachedOwner = aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage);
     mCachedUsage = usage;
   }
 
   *aNewUsage = usage;
 
@@ -475,90 +708,145 @@ nsDOMStoragePersistentDB::SetKey(nsDOMSt
 
 nsresult
 nsDOMStoragePersistentDB::SetSecure(nsDOMStorage* aStorage,
                                     const nsAString& aKey,
                                     const PRBool aSecure)
 {
   nsresult rv;
 
+  rv = EnsureLoadTemporaryTableForStorage(aStorage);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = EnsureInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mSetSecureStatement);
 
-  rv = mSetSecureStatement->BindInt32Parameter(0, aSecure ? 1 : 0);
+  Binder binder(mSetSecureStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    aStorage->GetScopeDBKey());
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mSetSecureStatement->BindUTF8StringParameter(1, aStorage->GetScopeDBKey());
+  rv = binder->BindStringByName(NS_LITERAL_CSTRING("key"),
+                                aKey);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = mSetSecureStatement->BindStringParameter(2, aKey);
+  rv = binder->BindInt32ByName(NS_LITERAL_CSTRING("secure"),
+                               aSecure ? 1 : 0);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return mSetSecureStatement->Execute();
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveKey(nsDOMStorage* aStorage,
                                     const nsAString& aKey,
                                     PRBool aExcludeOfflineFromUsage,
                                     PRInt32 aKeyUsage)
 {
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mRemoveKeyStatement);
 
   if (aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage) == mCachedOwner) {
     mCachedUsage -= aKeyUsage;
   }
 
-  nsresult rv = mRemoveKeyStatement->BindUTF8StringParameter(
-                                                0, aStorage->GetScopeDBKey());
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = mRemoveKeyStatement->BindStringParameter(1, aKey);
+  Binder binder(mRemoveKeyStatement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return mRemoveKeyStatement->Execute();
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    aStorage->GetScopeDBKey());
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = binder->BindStringByName(NS_LITERAL_CSTRING("key"),
+                                aKey);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder.Add();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mRemoveKeyStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::ClearStorage(nsDOMStorage* aStorage)
 {
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mRemoveStorageStatement);
 
   mCachedUsage = 0;
   mCachedOwner.Truncate();
 
-  nsresult rv;
+  Binder binder(mRemoveStorageStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mRemoveStorageStatement->BindUTF8StringParameter(
-                                                0, aStorage->GetScopeDBKey());
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    aStorage->GetScopeDBKey());
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return mRemoveStorageStatement->Execute();
+  rv = binder.Add();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mRemoveStorageStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveOwner(const nsACString& aOwner,
                                       PRBool aIncludeSubDomains)
 {
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mRemoveOwnerStatement);
 
   nsCAutoString subdomainsDBKey;
   nsDOMStorageDBWrapper::CreateDomainScopeDBKey(aOwner, subdomainsDBKey);
 
   if (!aIncludeSubDomains)
     subdomainsDBKey.AppendLiteral(":");
   subdomainsDBKey.AppendLiteral("*");
 
   if (subdomainsDBKey == mCachedOwner) {
     mCachedUsage = 0;
     mCachedOwner.Truncate();
   }
 
-  nsresult rv;
+  Binder binder(mRemoveOwnerStatement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = mRemoveOwnerStatement->BindUTF8StringParameter(0, subdomainsDBKey);
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
+                                    subdomainsDBKey);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return mRemoveOwnerStatement->Execute();
+  rv = binder.Add();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mRemoveOwnerStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 
 nsresult
 nsDOMStoragePersistentDB::RemoveOwners(const nsTArray<nsString> &aOwners,
                                        PRBool aIncludeSubDomains,
                                        PRBool aMatch)
 {
@@ -569,60 +857,89 @@ nsDOMStoragePersistentDB::RemoveOwners(c
 
     return RemoveAll();
   }
 
   // Using nsString here because it is going to be very long
   nsCString expression;
 
   if (aMatch) {
-    expression.AppendLiteral("DELETE FROM webappsstore2 WHERE scope IN (");
+    expression.AppendLiteral("DELETE FROM webappsstore2_view WHERE scope IN (");
   } else {
-    expression.AppendLiteral("DELETE FROM webappsstore2 WHERE scope NOT IN (");
+    expression.AppendLiteral("DELETE FROM webappsstore2_view WHERE scope NOT IN (");
   }
 
   for (PRUint32 i = 0; i < aOwners.Length(); i++) {
     if (i)
       expression.AppendLiteral(" UNION ");
 
     expression.AppendLiteral(
-      "SELECT DISTINCT scope FROM webappsstore2 WHERE scope GLOB ?");
+      "SELECT DISTINCT scope FROM webappsstore2_temp WHERE scope GLOB :scope");
+    expression.AppendInt(i);
+    expression.AppendLiteral(" UNION ");
+    expression.AppendLiteral(
+      "SELECT DISTINCT scope FROM webappsstore2 WHERE scope GLOB :scope");
+    expression.AppendInt(i);
   }
   expression.AppendLiteral(");");
 
   nsCOMPtr<mozIStorageStatement> statement;
 
-  nsresult rv = mConnection->CreateStatement(expression,
-                                             getter_AddRefs(statement));
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mConnection->CreateStatement(expression,
+                                    getter_AddRefs(statement));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  Binder binder(statement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   for (PRUint32 i = 0; i < aOwners.Length(); i++) {
     nsCAutoString quotaKey;
     rv = nsDOMStorageDBWrapper::CreateDomainScopeDBKey(
       NS_ConvertUTF16toUTF8(aOwners[i]), quotaKey);
 
     if (!aIncludeSubDomains)
       quotaKey.AppendLiteral(":");
     quotaKey.AppendLiteral("*");
 
-    rv = statement->BindUTF8StringParameter(i, quotaKey);
+    nsCAutoString paramName;
+    paramName.Assign("scope");
+    paramName.AppendInt(i);
+
+    rv = binder->BindUTF8StringByName(paramName, quotaKey);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  rv = binder.Add();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveAll()
 {
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozStorageStatementScoper scope(mRemoveAllStatement);
-  return mRemoveAllStatement->Execute();
+
+  rv = mRemoveAllStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetUsage(nsDOMStorage* aStorage,
                                    PRBool aExcludeOfflineFromUsage,
                                    PRInt32 *aUsage)
 {
   return GetUsageInternal(aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage),
@@ -652,27 +969,36 @@ nsDOMStoragePersistentDB::GetUsageIntern
                                            PRBool aExcludeOfflineFromUsage,
                                            PRInt32 *aUsage)
 {
   if (aQuotaDomainDBKey == mCachedOwner) {
     *aUsage = mCachedUsage;
     return NS_OK;
   }
 
+  nsresult rv;
+
+  rv = MaybeCommitInsertTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
   mozIStorageStatement* statement = aExcludeOfflineFromUsage
     ? mGetOfflineExcludedUsageStatement : mGetFullUsageStatement;
 
   mozStorageStatementScoper scope(statement);
 
-  nsresult rv;
-
   nsCAutoString scopeValue(aQuotaDomainDBKey);
   scopeValue += NS_LITERAL_CSTRING("*");
 
-  rv = statement->BindUTF8StringParameter(0, scopeValue);
+  Binder binder(statement, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"), scopeValue);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool exists;
   rv = statement->ExecuteStep(&exists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!exists) {
     *aUsage = 0;
@@ -684,8 +1010,49 @@ nsDOMStoragePersistentDB::GetUsageIntern
 
   if (!aQuotaDomainDBKey.IsEmpty()) {
     mCachedOwner = aQuotaDomainDBKey;
     mCachedUsage = *aUsage;
   }
 
   return NS_OK;
 }
+
+nsresult
+nsDOMStoragePersistentDB::EnsureInsertTransaction()
+{
+  if (!mConnection)
+    return NS_ERROR_UNEXPECTED;
+
+  PRBool transactionInProgress;
+  nsresult rv = mConnection->GetTransactionInProgress(&transactionInProgress);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (transactionInProgress)
+    return NS_OK;
+
+  rv = mConnection->BeginTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsDOMStoragePersistentDB::MaybeCommitInsertTransaction()
+{
+  if (!mConnection)
+    return NS_ERROR_UNEXPECTED;
+
+  PRBool transactionInProgress;
+  nsresult rv = mConnection->GetTransactionInProgress(&transactionInProgress);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("nsDOMStoragePersistentDB::MaybeCommitInsertTransaction: "
+               "connection probably already dead");
+  }
+
+  if (NS_FAILED(rv) || !transactionInProgress)
+    return NS_OK;
+
+  rv = mConnection->CommitTransaction();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
--- a/dom/src/storage/nsDOMStoragePersistentDB.h
+++ b/dom/src/storage/nsDOMStoragePersistentDB.h
@@ -45,22 +45,27 @@
 #include "nsTHashtable.h"
 
 class nsDOMStorage;
 class nsSessionStorageEntry;
 
 class nsDOMStoragePersistentDB
 {
 public:
-  nsDOMStoragePersistentDB() {}
+  nsDOMStoragePersistentDB();
   ~nsDOMStoragePersistentDB() {}
 
   nsresult
   Init(const nsString& aDatabaseName);
 
+  nsresult
+  EnsureLoadTemporaryTableForStorage(nsDOMStorage* aStorage);
+  nsresult
+  FlushAndDeleteTemporaryTableForStorage(nsDOMStorage* aStorage);
+
   /**
    * Retrieve a list of all the keys associated with a particular domain.
    */
   nsresult
   GetAllKeys(nsDOMStorage* aStorage,
              nsTHashtable<nsSessionStorageEntry>* aKeys);
 
   /**
@@ -141,24 +146,37 @@ public:
   nsresult
   GetUsage(const nsACString& aDomain, PRBool aIncludeSubDomains, PRInt32 *aUsage);
 
   /**
    * Clears all in-memory data from private browsing mode
    */
   nsresult ClearAllPrivateBrowsingData();
 
+  /**
+   * We process INSERTs in a transaction because of performance.
+   * If there is currently no transaction in progress, start one.
+   */
+  nsresult EnsureInsertTransaction();
+
+  /**
+   * If there is an INSERT transaction in progress, commit it now.
+   */
+  nsresult MaybeCommitInsertTransaction();
+
 protected:
 
   nsCOMPtr<mozIStorageConnection> mConnection;
 
+  nsCOMPtr<mozIStorageStatement> mCopyToTempTableStatement;
+  nsCOMPtr<mozIStorageStatement> mCopyBackToDiskStatement;
+  nsCOMPtr<mozIStorageStatement> mDeleteTemporaryTableStatement;
   nsCOMPtr<mozIStorageStatement> mGetAllKeysStatement;
   nsCOMPtr<mozIStorageStatement> mGetKeyValueStatement;
   nsCOMPtr<mozIStorageStatement> mInsertKeyStatement;
-  nsCOMPtr<mozIStorageStatement> mUpdateKeyStatement;
   nsCOMPtr<mozIStorageStatement> mSetSecureStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveKeyStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveOwnerStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveStorageStatement;
   nsCOMPtr<mozIStorageStatement> mRemoveAllStatement;
   nsCOMPtr<mozIStorageStatement> mGetOfflineExcludedUsageStatement;
   nsCOMPtr<mozIStorageStatement> mGetFullUsageStatement;