Bug 683316 - DOMStorageImpl::GetKey performance regression, r=bz
☠☠ backed out by 1e41259daf67 ☠ ☠
authorHonza Bambas <honzab.moz@firemni.cz>
Tue, 20 Sep 2011 20:44:11 +0200
changeset 78521 f94b4d73777fdf543a620a6f6744cdf32cca14fa
parent 78520 21a39c2f9060c50afcc1e3e593bd49b55345e19d
child 78522 69fdc6af563d6679be07ee8aeb2e9714b6697fb8
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs683316
milestone9.0a1
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 683316 - DOMStorageImpl::GetKey performance regression, r=bz
dom/src/storage/Makefile.in
dom/src/storage/nsDOMStorage.cpp
dom/src/storage/nsDOMStorage.h
dom/src/storage/nsDOMStorageBaseDB.cpp
dom/src/storage/nsDOMStorageBaseDB.h
dom/src/storage/nsDOMStorageDBWrapper.cpp
dom/src/storage/nsDOMStorageDBWrapper.h
dom/src/storage/nsDOMStorageMemoryDB.cpp
dom/src/storage/nsDOMStorageMemoryDB.h
dom/src/storage/nsDOMStoragePersistentDB.cpp
dom/src/storage/nsDOMStoragePersistentDB.h
--- a/dom/src/storage/Makefile.in
+++ b/dom/src/storage/Makefile.in
@@ -43,16 +43,17 @@ VPATH          = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 MODULE         = dom
 LIBRARY_NAME   = jsdomstorage_s
 LIBXUL_LIBRARY = 1
 
 CPPSRCS = \
        nsDOMStorage.cpp \
+       nsDOMStorageBaseDB.cpp \
        nsDOMStorageDBWrapper.cpp \
        nsDOMStoragePersistentDB.cpp \
        nsDOMStorageMemoryDB.cpp \
        StorageChild.cpp \
        StorageParent.cpp \
        $(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom
--- a/dom/src/storage/nsDOMStorage.cpp
+++ b/dom/src/storage/nsDOMStorage.cpp
@@ -721,17 +721,17 @@ DOMStorageImpl::DOMStorageImpl(nsDOMStor
   : DOMStorageBase(aThat)
 {
   Init(aStorage);
 }
 
 void
 DOMStorageImpl::Init(nsDOMStorage* aStorage)
 {
-  mItemsCached = PR_FALSE;
+  mItemsCachedVersion = 0;
   mItems.Init(8);
   mOwner = aStorage;
   if (nsDOMStorageManager::gStorageManager)
     nsDOMStorageManager::gStorageManager->AddToStoragesHash(this);
 }
 
 DOMStorageImpl::~DOMStorageImpl()
 {
@@ -887,18 +887,16 @@ DOMStorageImpl::SetDBValue(const nsAStri
                                   CanUseChromePersist());
 
   PRInt32 usage;
   rv = gStorageDB->SetKey(this, aKey, aValue, aSecure, quota,
                          !IS_PERMISSION_ALLOWED(offlineAppPermission),
                          &usage);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // 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) {
       nsCOMPtr<nsIScriptContext> scriptContext;
@@ -942,17 +940,17 @@ ClearStorageItem(nsSessionStorageEntry* 
   aEntry->mItem->SetValueInternal(EmptyString());
   return PL_DHASH_NEXT;
 }
 
 void
 DOMStorageImpl::ClearAll()
 {
   mItems.EnumerateEntries(ClearStorageItem, nsnull);
-  mItemsCached = PR_FALSE;
+  mItemsCachedVersion = 0;
 }
 
 struct CopyArgs {
   DOMStorageImpl* storage;
   bool callerSecure;
 };
 
 static PLDHashOperator
@@ -992,26 +990,26 @@ DOMStorageImpl::CloneFrom(bool aCallerSe
 }
 
 nsresult
 DOMStorageImpl::CacheKeysFromDB()
 {
   // cache all the keys in the hash. This is used by the Length and Key methods
   // use this cache for better performance. The disadvantage is that the
   // order may break if someone changes the keys in the database directly.
-  if (!mItemsCached) {
+  if (gStorageDB->IsScopeDirty(this)) {
     nsresult rv = InitDB();
     NS_ENSURE_SUCCESS(rv, rv);
 
     mItems.Clear();
 
     rv = gStorageDB->GetAllKeys(this, &mItems);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    mItemsCached = PR_TRUE;
+    gStorageDB->MarkScopeCached(this);
   }
 
   return NS_OK;
 }
 
 struct KeysArrayBuilderStruct
 {
   PRBool callerIsSecure;
@@ -1130,17 +1128,16 @@ DOMStorageImpl::GetKey(bool aCallerSecur
   // passed in.
 
   // XXX: This does a linear search for the key at index, which would
   // suck if there's a large numer of indexes. Do we care? If so,
   // maybe we need to have a lazily populated key array here or
   // something?
 
   if (UseDB()) {
-    mItemsCached = PR_FALSE;
     CacheKeysFromDB();
   }
 
   IndexFinderData data(aCallerSecure, aIndex);
   mItems.EnumerateEntries(IndexFinder, &data);
 
   if (!data.mItem) {
     // aIndex was larger than the number of accessible keys. Throw.
@@ -1250,18 +1247,16 @@ DOMStorageImpl::RemoveValue(bool aCaller
     if (!aCallerSecure && secureItem)
       return NS_ERROR_DOM_SECURITY_ERR;
 
     oldValue = value;
 
     rv = gStorageDB->RemoveKey(this, aKey, !IsOfflineAllowed(mDomain),
                                aKey.Length() + value.Length());
     NS_ENSURE_SUCCESS(rv, rv);
-
-    // Before bug 536544 got fixed we were dropping mItemsCached flag here
   }
   else if (entry) {
     // clear string as StorageItems may be referencing this item
     oldValue = entry->mItem->GetValueInternal();
     entry->mItem->ClearValue();
   }
 
   if (entry) {
--- a/dom/src/storage/nsDOMStorage.h
+++ b/dom/src/storage/nsDOMStorage.h
@@ -265,16 +265,19 @@ public:
   virtual nsresult SetValue(bool aCallerSecure, const nsAString& aKey,
                             const nsAString& aData, nsAString& aOldValue);
   virtual nsresult RemoveValue(bool aCallerSecure, const nsAString& aKey,
                                nsAString& aOldValue);
   virtual nsresult Clear(bool aCallerSecure, PRInt32* aOldCount);
 
   // cache the keys from the database for faster lookup
   nsresult CacheKeysFromDB();
+
+  PRUint64 CachedVersion() { return mItemsCachedVersion; }
+  void SetCachedVersion(PRUint64 version) { mItemsCachedVersion = version; }
   
   // Some privileged internal pages can use a persistent storage even in
   // session-only or private-browsing modes.
   bool CanUseChromePersist();
 
   // retrieve the value and secure state corresponding to a key out of storage
   // that has been cached in mItems hash table.
   nsresult
@@ -323,18 +326,19 @@ private:
                      const nsACString& aScopeDBKey,
                      const nsACString& aQuotaDomainDBKey,
                      const nsACString& aQuotaETLDplus1DomainDBKey,
                      PRUint32 aStorageType);
   void SetSessionOnly(bool aSessionOnly);
 
   static nsresult InitDB();
 
-  // true if items from the database are cached
-  PRPackedBool mItemsCached;
+  // 0 initially or a positive data version number assigned by gStorageDB
+  // after keys have been cached from the database
+  PRUint64 mItemsCachedVersion;
 
   // the key->value item pairs
   nsTHashtable<nsSessionStorageEntry> mItems;
 
   // Weak reference to the owning storage instance
   nsDOMStorage* mOwner;
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/nsDOMStorageBaseDB.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Honza Bambas <honzab@firemni.cz>
+ *
+ * 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 ***** */
+
+#include "nsDOMStorageBaseDB.h"
+#include "nsDOMStorage.h"
+
+PRUint64 nsDOMStorageBaseDB::sGlobalVersion = 1;
+
+nsDOMStorageBaseDB::nsDOMStorageBaseDB()
+{
+  mScopesVersion.Init(8);
+}
+
+// public
+
+void
+nsDOMStorageBaseDB::MarkScopeCached(DOMStorageImpl* aStorage)
+{
+  aStorage->SetCachedVersion(CachedScopeVersion(aStorage));
+}
+
+bool
+nsDOMStorageBaseDB::IsScopeDirty(DOMStorageImpl* aStorage)
+{
+  return !aStorage->CachedVersion() ||
+         (aStorage->CachedVersion() != CachedScopeVersion(aStorage));
+}
+
+// protected
+
+// static
+PRUint64
+nsDOMStorageBaseDB::NextGlobalVersion()
+{
+  sGlobalVersion++;
+  if (sGlobalVersion == 0) // Control overlap, never return 0
+    sGlobalVersion = 1;
+  return sGlobalVersion;
+}
+
+PRUint64
+nsDOMStorageBaseDB::CachedScopeVersion(DOMStorageImpl* aStorage)
+{
+  PRUint64 currentVersion;
+  if (mScopesVersion.Get(aStorage->GetScopeDBKey(), &currentVersion))
+    return currentVersion;
+
+  mScopesVersion.Put(aStorage->GetScopeDBKey(), sGlobalVersion);
+  return sGlobalVersion;
+}
+
+void
+nsDOMStorageBaseDB::MarkScopeDirty(DOMStorageImpl* aStorage)
+{
+  PRUint64 nextVersion = NextGlobalVersion();
+  mScopesVersion.Put(aStorage->GetScopeDBKey(), nextVersion);
+
+  // We may do this because the storage updates its cache along with
+  // updating the database.
+  aStorage->SetCachedVersion(nextVersion);
+}
+
+void
+nsDOMStorageBaseDB::MarkAllScopesDirty()
+{
+  mScopesVersion.Clear();
+  NextGlobalVersion();
+}
new file mode 100644
--- /dev/null
+++ b/dom/src/storage/nsDOMStorageBaseDB.h
@@ -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 mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Honza Bambas <honzab@firemni.cz>
+ *
+ * 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 ***** */
+
+#ifndef nsDOMStorageBaseDB_h___
+#define nsDOMStorageBaseDB_h___
+
+#include "nscore.h"
+#include "nsDataHashtable.h"
+
+class DOMStorageImpl;
+
+class nsDOMStorageBaseDB
+{
+public:
+  nsDOMStorageBaseDB();
+  virtual ~nsDOMStorageBaseDB() {}
+
+  /**
+   * Marks the storage as "cached" after the DOMStorageImpl object has loaded
+   * all items to its memory copy of the entries - IsScopeDirty returns false
+   * after call of this method for this storage.
+   *
+   * When a key is changed or deleted in the storage, the storage scope is
+   * marked as "dirty" again and makes the DOMStorageImpl object recache its
+   * keys on next access, because IsScopeDirty returns true again.
+   */
+  void MarkScopeCached(DOMStorageImpl* aStorage);
+
+  /**
+   * Test whether the storage for the scope (i.e. origin or host) has been
+   * changed since the last MarkScopeCached call.
+   */
+  bool IsScopeDirty(DOMStorageImpl* aStorage);
+
+protected:
+  nsDataHashtable<nsCStringHashKey, PRUint64> mScopesVersion;
+
+  static PRUint64 NextGlobalVersion();
+  PRUint64 CachedScopeVersion(DOMStorageImpl* aStorage);
+
+  void MarkScopeDirty(DOMStorageImpl* aStorage);
+  void MarkAllScopesDirty();
+
+private:
+  static PRUint64 sGlobalVersion;
+};
+
+#endif /* nsDOMStorageDB_h___ */
--- a/dom/src/storage/nsDOMStorageDBWrapper.cpp
+++ b/dom/src/storage/nsDOMStorageDBWrapper.cpp
@@ -108,27 +108,33 @@ nsDOMStorageDBWrapper::FlushAndDeleteTem
   // Everything flushed?  Then no need for a timer.
   if (!mChromePersistentDB.mTempTableLoads.Count() && 
       !mPersistentDB.mTempTableLoads.Count())
     StopTempTableFlushTimer();
 
   return NS_FAILED(rv1) ? rv1 : rv2;
 }
 
-#define IMPL_FORWARDER(_code)                                         \
+#define IMPL_FORWARDER_GUTS(_return, _code)                                \
   PR_BEGIN_MACRO                                                      \
   if (aStorage->CanUseChromePersist())                                \
-    return mChromePersistentDB._code;                                 \
+    _return mChromePersistentDB._code;                                \
   if (nsDOMStorageManager::gStorageManager->InPrivateBrowsingMode())  \
-    return mPrivateBrowsingDB._code;                                  \
+    _return mPrivateBrowsingDB._code;                                 \
   if (aStorage->SessionOnly())                                        \
-    return mSessionOnlyDB._code;                                      \
-  return mPersistentDB._code;                                         \
+    _return mSessionOnlyDB._code;                                     \
+  _return mPersistentDB._code;                                        \
   PR_END_MACRO
 
+#define IMPL_FORWARDER(_code)                                  \
+  IMPL_FORWARDER_GUTS(return, _code)
+
+#define IMPL_VOID_FORWARDER(_code)                                    \
+  IMPL_FORWARDER_GUTS((void), _code)
+
 nsresult
 nsDOMStorageDBWrapper::GetAllKeys(DOMStorageImpl* aStorage,
                                   nsTHashtable<nsSessionStorageEntry>* aKeys)
 {
   IMPL_FORWARDER(GetAllKeys(aStorage, aKeys));
 }
 
 nsresult
@@ -171,16 +177,28 @@ nsDOMStorageDBWrapper::RemoveKey(DOMStor
 }
 
 nsresult
 nsDOMStorageDBWrapper::ClearStorage(DOMStorageImpl* aStorage)
 {
   IMPL_FORWARDER(ClearStorage(aStorage));
 }
 
+void
+nsDOMStorageDBWrapper::MarkScopeCached(DOMStorageImpl* aStorage)
+{
+  IMPL_VOID_FORWARDER(MarkScopeCached(aStorage));
+}
+
+bool
+nsDOMStorageDBWrapper::IsScopeDirty(DOMStorageImpl* aStorage)
+{
+  IMPL_FORWARDER(IsScopeDirty(aStorage));
+}
+
 nsresult
 nsDOMStorageDBWrapper::DropSessionOnlyStoragesForHost(const nsACString& aHostName)
 {
   return mSessionOnlyDB.RemoveOwner(aHostName, PR_TRUE);
 }
 
 nsresult
 nsDOMStorageDBWrapper::DropPrivateBrowsingStorages()
--- a/dom/src/storage/nsDOMStorageDBWrapper.h
+++ b/dom/src/storage/nsDOMStorageDBWrapper.h
@@ -186,16 +186,35 @@ public:
 
   /**
     * Returns usage of the domain and optionaly by any subdomain.
     */
   nsresult
   GetUsage(const nsACString& aDomain, PRBool aIncludeSubDomains, PRInt32 *aUsage);
 
   /**
+   * Marks the storage as "cached" after the DOMStorageImpl object has loaded
+   * all items to its memory copy of the entries - IsScopeDirty returns false
+   * after call of this method for this storage.
+   *
+   * When a key is changed or deleted in the storage, the storage scope is
+   * marked as "dirty" again and makes the DOMStorageImpl object recache its
+   * keys on next access, because IsScopeDirty returns true again.
+   */
+  void
+  MarkScopeCached(DOMStorageImpl* aStorage);
+
+  /**
+   * Test whether the storage for the scope (i.e. origin or host) has been
+   * changed since the last MarkScopeCached call.
+   */
+  bool
+  IsScopeDirty(DOMStorageImpl* aStorage);
+
+  /**
     * Turns "http://foo.bar.com:80" to "moc.rab.oof.:http:80",
     * i.e. reverses the host, appends a dot, appends the schema
     * and a port number.
     */
   static nsresult CreateOriginScopeDBKey(nsIURI* aUri, nsACString& aKey);
 
   /**
     * Turns "http://foo.bar.com" to "moc.rab.oof.",
--- a/dom/src/storage/nsDOMStorageMemoryDB.cpp
+++ b/dom/src/storage/nsDOMStorageMemoryDB.cpp
@@ -230,16 +230,18 @@ nsDOMStorageMemoryDB::SetKey(DOMStorageI
 
   storage->mUsageDelta += aValue.Length() - item->mValue.Length();
 
   item->mValue = aValue;
   item->mSecure = aSecure;
 
   *aNewUsage = usage;
 
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::SetSecure(DOMStorageImpl* aStorage,
                                 const nsAString& aKey,
                                 const PRBool aSecure)
 {
@@ -250,16 +252,18 @@ nsDOMStorageMemoryDB::SetSecure(DOMStora
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsInMemoryItem* item;
   if (!storage->mTable.Get(aKey, &item))
     return NS_ERROR_DOM_NOT_FOUND_ERR;
 
   item->mSecure = aSecure;
 
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::RemoveKey(DOMStorageImpl* aStorage,
                                 const nsAString& aKey,
                                 PRBool aExcludeOfflineFromUsage,
                                 PRInt32 aKeyUsage)
@@ -272,16 +276,18 @@ nsDOMStorageMemoryDB::RemoveKey(DOMStora
 
   nsInMemoryItem* item;
   if (!storage->mTable.Get(aKey, &item))
     return NS_ERROR_DOM_NOT_FOUND_ERR;
 
   storage->mUsageDelta -= aKey.Length() + item->mValue.Length();
   storage->mTable.Remove(aKey);
 
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 static PLDHashOperator
 RemoveAllKeysEnum(const nsAString& keyname,
                   nsAutoPtr<nsDOMStorageMemoryDB::nsInMemoryItem>& item,
                   void *closure)
 {
@@ -297,23 +303,27 @@ nsDOMStorageMemoryDB::ClearStorage(DOMSt
 {
   nsresult rv;
 
   nsInMemoryStorage* storage;
   rv = GetItemsTable(aStorage, &storage);
   NS_ENSURE_SUCCESS(rv, rv);
 
   storage->mTable.Enumerate(RemoveAllKeysEnum, storage);
+
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::DropStorage(DOMStorageImpl* aStorage)
 {
   mData.Remove(aStorage->GetScopeDBKey());
+  MarkScopeDirty(aStorage);
   return NS_OK;
 }
 
 struct RemoveOwnersStruc
 {
   nsCString* mSubDomain;
   PRBool mMatch;
 };
@@ -341,16 +351,18 @@ nsDOMStorageMemoryDB::RemoveOwner(const 
   if (!aIncludeSubDomains)
     subdomainsDBKey.AppendLiteral(":");
 
   RemoveOwnersStruc struc;
   struc.mSubDomain = &subdomainsDBKey;
   struc.mMatch = PR_TRUE;
   mData.Enumerate(RemoveOwnersEnum, &struc);
 
+  MarkAllScopesDirty();
+
   return NS_OK;
 }
 
 
 nsresult
 nsDOMStorageMemoryDB::RemoveOwners(const nsTArray<nsString> &aOwners,
                                    PRBool aIncludeSubDomains,
                                    PRBool aMatch)
@@ -373,23 +385,28 @@ nsDOMStorageMemoryDB::RemoveOwners(const
       quotaKey.AppendLiteral(":");
 
     RemoveOwnersStruc struc;
     struc.mSubDomain = &quotaKey;
     struc.mMatch = aMatch;
     mData.Enumerate(RemoveOwnersEnum, &struc);
   }
 
+  MarkAllScopesDirty();
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::RemoveAll()
 {
   mData.Clear(); // XXX Check this releases all instances
+
+  MarkAllScopesDirty();
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStorageMemoryDB::GetUsage(DOMStorageImpl* aStorage,
                                PRBool aExcludeOfflineFromUsage, PRInt32 *aUsage)
 {
   return GetUsageInternal(aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage),
--- a/dom/src/storage/nsDOMStorageMemoryDB.h
+++ b/dom/src/storage/nsDOMStorageMemoryDB.h
@@ -35,22 +35,23 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsDOMStorageMemoryDB_h___
 #define nsDOMStorageMemoryDB_h___
 
 #include "nscore.h"
+#include "nsDOMStorageBaseDB.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 
 class nsDOMStoragePersistentDB;
 
-class nsDOMStorageMemoryDB
+class nsDOMStorageMemoryDB : public nsDOMStorageBaseDB
 {
 public:
   nsDOMStorageMemoryDB() : mPreloading(PR_FALSE) {}
   ~nsDOMStorageMemoryDB() {}
 
   class nsInMemoryItem
   {
   public:
--- a/dom/src/storage/nsDOMStoragePersistentDB.cpp
+++ b/dom/src/storage/nsDOMStoragePersistentDB.cpp
@@ -721,16 +721,18 @@ nsDOMStoragePersistentDB::SetKey(DOMStor
 
   if (!aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage).IsEmpty()) {
     mCachedOwner = aStorage->GetQuotaDomainDBKey(!aExcludeOfflineFromUsage);
     mCachedUsage = usage;
   }
 
   *aNewUsage = usage;
 
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::SetSecure(DOMStorageImpl* aStorage,
                                     const nsAString& aKey,
                                     const PRBool aSecure)
 {
@@ -755,17 +757,22 @@ nsDOMStoragePersistentDB::SetSecure(DOMS
   NS_ENSURE_SUCCESS(rv, rv);
   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();
+  rv = mSetSecureStatement->Execute();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  MarkScopeDirty(aStorage);
+
+  return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveKey(DOMStorageImpl* aStorage,
                                     const nsAString& aKey,
                                     PRBool aExcludeOfflineFromUsage,
                                     PRInt32 aKeyUsage)
 {
@@ -791,16 +798,18 @@ nsDOMStoragePersistentDB::RemoveKey(DOMS
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mRemoveKeyStatement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::ClearStorage(DOMStorageImpl* aStorage)
 {
   nsresult rv;
 
@@ -820,16 +829,18 @@ nsDOMStoragePersistentDB::ClearStorage(D
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mRemoveStorageStatement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  MarkScopeDirty(aStorage);
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveOwner(const nsACString& aOwner,
                                       PRBool aIncludeSubDomains)
 {
   nsresult rv;
@@ -859,16 +870,18 @@ nsDOMStoragePersistentDB::RemoveOwner(co
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mRemoveOwnerStatement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  MarkAllScopesDirty();
+
   return NS_OK;
 }
 
 
 nsresult
 nsDOMStoragePersistentDB::RemoveOwners(const nsTArray<nsString> &aOwners,
                                        PRBool aIncludeSubDomains,
                                        PRBool aMatch)
@@ -936,32 +949,36 @@ nsDOMStoragePersistentDB::RemoveOwners(c
   }
 
   rv = binder.Add();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  MarkAllScopesDirty();
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::RemoveAll()
 {
   nsresult rv;
 
   rv = MaybeCommitInsertTransaction();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mozStorageStatementScoper scope(mRemoveAllStatement);
 
   rv = mRemoveAllStatement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  MarkAllScopesDirty();
+
   return NS_OK;
 }
 
 nsresult
 nsDOMStoragePersistentDB::GetUsage(DOMStorageImpl* aStorage,
                                    PRBool aExcludeOfflineFromUsage,
                                    PRInt32 *aUsage)
 {
--- a/dom/src/storage/nsDOMStoragePersistentDB.h
+++ b/dom/src/storage/nsDOMStoragePersistentDB.h
@@ -35,29 +35,30 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsDOMStoragePersistentDB_h___
 #define nsDOMStoragePersistentDB_h___
 
 #include "nscore.h"
+#include "nsDOMStorageBaseDB.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatement.h"
 #include "nsTHashtable.h"
 #include "nsDataHashtable.h"
 #include "mozilla/TimeStamp.h"
 
 class DOMStorageImpl;
 class nsSessionStorageEntry;
 
 using mozilla::TimeStamp;
 using mozilla::TimeDuration;
 
-class nsDOMStoragePersistentDB
+class nsDOMStoragePersistentDB : public nsDOMStorageBaseDB
 {
 public:
   nsDOMStoragePersistentDB();
   ~nsDOMStoragePersistentDB() {}
 
   nsresult
   Init(const nsString& aDatabaseName);